| /* |
| * Copyright 2018 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. |
| */ |
| |
| #define LOG_TAG "netd_hidl_test" |
| |
| #include <linux/if.h> |
| |
| #include <android-base/stringprintf.h> |
| #include <android/system/net/netd/1.1/INetd.h> |
| #include <gtest/gtest.h> |
| #include <hidl/GtestPrinter.h> |
| #include <hidl/ServiceManagement.h> |
| #include <log/log.h> |
| #include <netutils/ifc.h> |
| |
| #include "VtsHalNetNetdTestUtils.h" |
| #include "tun_interface.h" |
| |
| using android::sp; |
| using android::base::StringPrintf; |
| using android::hardware::Return; |
| using android::net::TunInterface; |
| using android::system::net::netd::V1_1::INetd; |
| |
| namespace { |
| const net_handle_t INVALID_NET_HANDLE = 0x6600FACADE; |
| |
| constexpr const char* IPV4_ROUTER = "192.0.2.1"; |
| constexpr const char* IPV4_CONNECTED = "192.0.2.0/25"; |
| constexpr const char* IPV4_SUBNET_1 = "192.0.2.192/28"; |
| constexpr const char* IPV4_HOST_1 = "192.0.2.195"; |
| constexpr const char* IPV4_SUBNET_2 = "192.0.2.240/28"; |
| constexpr const char* IPV4_HOST_2 = "192.0.2.245"; |
| constexpr const char* IPV4_UNREACHABLE = "192.0.2.239"; |
| |
| constexpr const char* IPV6_ROUTER = "2001:db8::cafe"; |
| constexpr const char* IPV6_CONNECTED = "2001:db8::/64"; |
| constexpr const char* IPV6_SUBNET_1 = "2001:db8:babe::/48"; |
| constexpr const char* IPV6_HOST_1 = "2001:db8:babe::1"; |
| constexpr const char* IPV6_SUBNET_2 = "2001:db8:d00d::/48"; |
| constexpr const char* IPV6_HOST_2 = "2001:db8:d00d::1"; |
| constexpr const char* IPV6_UNREACHABLE = "2001:db8:d0a::"; |
| |
| std::vector<const char*> REACHABLE = { |
| IPV4_ROUTER, IPV4_HOST_1, IPV4_HOST_2, IPV6_ROUTER, IPV6_HOST_1, IPV6_HOST_2, |
| }; |
| |
| void checkAllReachable(net_handle_t handle) { |
| int ret; |
| for (const auto& dst : REACHABLE) { |
| ret = checkReachability(handle, dst); |
| EXPECT_EQ(0, ret) << "Expected reachability to " << dst << " but got %s" << strerror(-ret); |
| } |
| for (const auto& dst : {IPV4_UNREACHABLE, IPV6_UNREACHABLE}) { |
| EXPECT_EQ(-ENETUNREACH, checkReachability(handle, dst)) |
| << "Expected " << dst << " to be unreachable, but was reachable"; |
| } |
| } |
| |
| void checkAllUnreachable(net_handle_t handle) { |
| for (const auto& dst : REACHABLE) { |
| EXPECT_EQ(-ENETUNREACH, checkReachability(handle, dst)); |
| } |
| for (const auto& dst : {IPV4_UNREACHABLE, IPV6_UNREACHABLE}) { |
| EXPECT_EQ(-ENETUNREACH, checkReachability(handle, dst)); |
| } |
| } |
| } // namespace |
| |
| class NetdHidlTest : public ::testing::TestWithParam<std::string> { |
| public: |
| // Netd HAL instance. |
| sp<INetd> netd; |
| |
| // The nethandle of our test network, and its packet mark. |
| net_handle_t mNetHandle; |
| uint32_t mPacketMark; |
| |
| // Two interfaces that we can add and remove from our test network. |
| static TunInterface sTun1; |
| static TunInterface sTun2; |
| |
| // The interface name of sTun1, for convenience. |
| static const char* sIfaceName; |
| |
| static void SetUpTestCase() { |
| ASSERT_EQ(0, sTun1.init()); |
| ASSERT_EQ(0, sTun2.init()); |
| ASSERT_LE(sTun1.name().size(), static_cast<size_t>(IFNAMSIZ)); |
| ASSERT_LE(sTun2.name().size(), static_cast<size_t>(IFNAMSIZ)); |
| ifc_init(); |
| ASSERT_EQ(0, ifc_up(sTun1.name().c_str())); |
| ASSERT_EQ(0, ifc_up(sTun2.name().c_str())); |
| sIfaceName = sTun1.name().c_str(); |
| } |
| |
| static void TearDownTestCase() { |
| sTun1.destroy(); |
| sTun2.destroy(); |
| ifc_close(); |
| } |
| |
| virtual void SetUp() override { |
| netd = INetd::getService(GetParam()); |
| |
| ASSERT_NE(netd, nullptr) << "Could not get HIDL instance"; |
| |
| // Set up an OEM network. |
| INetd::StatusCode status; |
| Return<void> ret = |
| netd->createOemNetwork([&](net_handle_t n, uint32_t p, INetd::StatusCode s) { |
| status = s; |
| mNetHandle = n; |
| mPacketMark = p; |
| }); |
| ASSERT_TRUE(ret.isOk()); |
| ASSERT_EQ(INetd::StatusCode::OK, status); |
| ASSERT_NE(NETWORK_UNSPECIFIED, mNetHandle); |
| ASSERT_NE((uint32_t)0, mPacketMark); |
| } |
| |
| virtual void TearDown() override { netd->destroyOemNetwork(mNetHandle); } |
| |
| void expectAddRoute(INetd::StatusCode expectedStatus, net_handle_t handle, const char* iface, |
| const char* destination, const char* nexthop) { |
| Return<INetd::StatusCode> retStatus = |
| netd->addRouteToOemNetwork(handle, iface, destination, nexthop); |
| EXPECT_STATUS(expectedStatus, retStatus); |
| } |
| |
| void expectAddRouteSuccess(net_handle_t h, const char* i, const char* d, const char* n) { |
| expectAddRoute(INetd::StatusCode::OK, h, i, d, n); |
| } |
| |
| void expectRemoveRoute(INetd::StatusCode expectedStatus, net_handle_t handle, const char* iface, |
| const char* destination, const char* nexthop) { |
| Return<INetd::StatusCode> retStatus = |
| netd->removeRouteFromOemNetwork(handle, iface, destination, nexthop); |
| EXPECT_STATUS(expectedStatus, retStatus); |
| } |
| |
| void expectRemoveRouteSuccess(net_handle_t h, const char* i, const char* d, const char* n) { |
| expectRemoveRoute(INetd::StatusCode::OK, h, i, d, n); |
| } |
| }; |
| |
| TunInterface NetdHidlTest::sTun1; |
| TunInterface NetdHidlTest::sTun2; |
| const char* NetdHidlTest::sIfaceName; |
| |
| // Tests adding and removing interfaces from the OEM network. |
| TEST_P(NetdHidlTest, TestAddRemoveInterfaces) { |
| // HACK: mark out permissions bits. |
| uint32_t packetMark = mPacketMark & 0xffff; |
| |
| EXPECT_EQ(0, checkNetworkExists(mNetHandle)); |
| EXPECT_EQ(0, countRulesForFwmark(packetMark)); |
| |
| // Adding an interface creates the routing rules. |
| Return<INetd::StatusCode> retStatus = netd->addInterfaceToOemNetwork(mNetHandle, sIfaceName); |
| EXPECT_TRUE(retStatus.isOk()); |
| EXPECT_EQ(0, checkNetworkExists(mNetHandle)); |
| EXPECT_EQ(2, countRulesForFwmark(packetMark)); |
| |
| // Adding an interface again silently succeeds. |
| retStatus = netd->addInterfaceToOemNetwork(mNetHandle, sIfaceName); |
| EXPECT_TRUE(retStatus.isOk()); |
| EXPECT_EQ(0, checkNetworkExists(mNetHandle)); |
| EXPECT_EQ(2, countRulesForFwmark(packetMark)); |
| |
| // More than one network can be created. |
| net_handle_t netHandle2; |
| uint32_t packetMark2; |
| |
| Return<void> ret = netd->createOemNetwork([&](net_handle_t n, uint32_t p, INetd::StatusCode) { |
| netHandle2 = n; |
| packetMark2 = p & 0xffff; |
| }); |
| EXPECT_TRUE(ret.isOk()); |
| EXPECT_NE(mNetHandle, netHandle2); |
| EXPECT_NE(packetMark, packetMark2); |
| EXPECT_EQ(0, checkNetworkExists(netHandle2)); |
| EXPECT_EQ(0, countRulesForFwmark(packetMark2)); |
| |
| // An interface can only be in one network. |
| retStatus = netd->addInterfaceToOemNetwork(netHandle2, sIfaceName); |
| EXPECT_STATUS(INetd::StatusCode::UNKNOWN_ERROR, retStatus); |
| |
| // Removing the interface removes the rules. |
| retStatus = netd->removeInterfaceFromOemNetwork(mNetHandle, sIfaceName); |
| EXPECT_STATUS(INetd::StatusCode::OK, retStatus); |
| EXPECT_EQ(0, countRulesForFwmark(packetMark)); |
| |
| retStatus = netd->addInterfaceToOemNetwork(netHandle2, sIfaceName); |
| EXPECT_STATUS(INetd::StatusCode::OK, retStatus); |
| EXPECT_EQ(2, countRulesForFwmark(packetMark2)); |
| |
| // When a network is removed the interfaces are deleted. |
| retStatus = netd->destroyOemNetwork(netHandle2); |
| EXPECT_STATUS(INetd::StatusCode::OK, retStatus); |
| EXPECT_EQ(-ENONET, checkNetworkExists(netHandle2)); |
| EXPECT_EQ(0, countRulesForFwmark(packetMark2)); |
| |
| // Adding an interface to a non-existent network fails. |
| retStatus = netd->addInterfaceToOemNetwork(INVALID_NET_HANDLE, sIfaceName); |
| EXPECT_STATUS(INetd::StatusCode::INVALID_ARGUMENTS, retStatus); |
| retStatus = netd->removeInterfaceFromOemNetwork(INVALID_NET_HANDLE, sIfaceName); |
| EXPECT_STATUS(INetd::StatusCode::INVALID_ARGUMENTS, retStatus); |
| } |
| |
| // Tests adding and removing routes from the OEM network. |
| TEST_P(NetdHidlTest, TestAddRemoveRoutes) { |
| Return<INetd::StatusCode> retStatus = netd->addInterfaceToOemNetwork(mNetHandle, sIfaceName); |
| ASSERT_TRUE(retStatus.isOk()); |
| |
| // Network exists, but has no routes and no connectivity. |
| EXPECT_EQ(0, checkNetworkExists(mNetHandle)); |
| checkAllUnreachable(mNetHandle); |
| |
| // Add a directly-connected route and two gatewayed routes through it. |
| expectAddRouteSuccess(mNetHandle, sIfaceName, IPV4_CONNECTED, ""); |
| expectAddRouteSuccess(mNetHandle, sIfaceName, IPV4_SUBNET_1, IPV4_ROUTER); |
| expectAddRouteSuccess(mNetHandle, sIfaceName, IPV4_SUBNET_2, IPV4_ROUTER); |
| expectAddRouteSuccess(mNetHandle, sIfaceName, IPV6_CONNECTED, ""); |
| expectAddRouteSuccess(mNetHandle, sIfaceName, IPV6_SUBNET_1, IPV6_ROUTER); |
| expectAddRouteSuccess(mNetHandle, sIfaceName, IPV6_SUBNET_2, IPV6_ROUTER); |
| |
| // Test some destinations. |
| checkAllReachable(mNetHandle); |
| |
| // Remove the directly-connected routes and everything is unreachable again. |
| expectRemoveRouteSuccess(mNetHandle, sIfaceName, IPV4_CONNECTED, ""); |
| expectRemoveRouteSuccess(mNetHandle, sIfaceName, IPV6_CONNECTED, ""); |
| expectRemoveRouteSuccess(mNetHandle, sIfaceName, IPV4_SUBNET_1, IPV4_ROUTER); |
| expectRemoveRouteSuccess(mNetHandle, sIfaceName, IPV4_SUBNET_2, IPV4_ROUTER); |
| expectRemoveRouteSuccess(mNetHandle, sIfaceName, IPV6_SUBNET_1, IPV6_ROUTER); |
| expectRemoveRouteSuccess(mNetHandle, sIfaceName, IPV6_SUBNET_2, IPV6_ROUTER); |
| |
| checkAllUnreachable(mNetHandle); |
| |
| // Invalid: route doesn't exist so can't be deleted. |
| expectRemoveRoute(INetd::StatusCode::UNKNOWN_ERROR, mNetHandle, sIfaceName, IPV4_CONNECTED, ""); |
| |
| // Invalid: IP address instead of prefix. |
| expectAddRoute(INetd::StatusCode::INVALID_ARGUMENTS, mNetHandle, sIfaceName, IPV4_HOST_1, ""); |
| expectAddRoute(INetd::StatusCode::INVALID_ARGUMENTS, mNetHandle, sIfaceName, IPV6_HOST_1, ""); |
| |
| // Invalid: both nexthop and interface are empty. |
| expectAddRoute(INetd::StatusCode::UNKNOWN_ERROR, mNetHandle, "", IPV4_SUBNET_1, ""); |
| expectAddRoute(INetd::StatusCode::UNKNOWN_ERROR, mNetHandle, "", IPV6_SUBNET_1, ""); |
| |
| // The kernel deletes the routes when the interfaces go away. |
| } |
| |
| // Tests enabling and disabling forwarding between interfaces. |
| TEST_P(NetdHidlTest, TestForwarding) { |
| Return<INetd::StatusCode> retStatus = |
| netd->addInterfaceToOemNetwork(mNetHandle, sTun1.name().c_str()); |
| EXPECT_STATUS(INetd::StatusCode::OK, retStatus); |
| retStatus = netd->addInterfaceToOemNetwork(mNetHandle, sTun2.name().c_str()); |
| EXPECT_STATUS(INetd::StatusCode::OK, retStatus); |
| |
| // TODO: move this test to netd and use ROUTE_TABLE_OFFSET_FROM_INDEX directly. |
| uint32_t table1 = 1000 + sTun1.ifindex(); |
| uint32_t table2 = 1000 + sTun1.ifindex(); |
| const char* regexTemplate = "from all iif %s .*lookup (%s|%d)"; |
| std::string regex1 = |
| StringPrintf(regexTemplate, sTun1.name().c_str(), sTun2.name().c_str(), table2); |
| std::string regex2 = |
| StringPrintf(regexTemplate, sTun2.name().c_str(), sTun1.name().c_str(), table1); |
| |
| EXPECT_EQ(0, countMatchingIpRules(regex1)); |
| EXPECT_EQ(0, countMatchingIpRules(regex2)); |
| |
| retStatus = netd->setForwardingBetweenInterfaces(sTun1.name(), sTun2.name(), true); |
| EXPECT_STATUS(INetd::StatusCode::OK, retStatus); |
| EXPECT_EQ(2, countMatchingIpRules(regex1)); |
| EXPECT_EQ(0, countMatchingIpRules(regex2)); |
| |
| // No attempt at deduplicating rules is made. |
| retStatus = netd->setForwardingBetweenInterfaces(sTun1.name(), sTun2.name(), true); |
| EXPECT_STATUS(INetd::StatusCode::OK, retStatus); |
| EXPECT_EQ(4, countMatchingIpRules(regex1)); |
| |
| retStatus = netd->setForwardingBetweenInterfaces(sTun1.name(), sTun2.name(), false); |
| EXPECT_STATUS(INetd::StatusCode::OK, retStatus); |
| EXPECT_EQ(2, countMatchingIpRules(regex1)); |
| |
| retStatus = netd->setForwardingBetweenInterfaces(sTun2.name(), sTun1.name(), true); |
| EXPECT_STATUS(INetd::StatusCode::OK, retStatus); |
| EXPECT_EQ(2, countMatchingIpRules(regex1)); |
| EXPECT_EQ(2, countMatchingIpRules(regex2)); |
| |
| retStatus = netd->setForwardingBetweenInterfaces(sTun1.name(), sTun2.name(), false); |
| EXPECT_STATUS(INetd::StatusCode::OK, retStatus); |
| EXPECT_EQ(0, countMatchingIpRules(regex1)); |
| EXPECT_EQ(2, countMatchingIpRules(regex2)); |
| |
| retStatus = netd->setForwardingBetweenInterfaces(sTun2.name(), sTun1.name(), false); |
| EXPECT_STATUS(INetd::StatusCode::OK, retStatus); |
| EXPECT_EQ(0, countMatchingIpRules(regex1)); |
| EXPECT_EQ(0, countMatchingIpRules(regex2)); |
| |
| // Deleting rules that don't exist fails. |
| retStatus = netd->setForwardingBetweenInterfaces(sTun1.name(), sTun2.name(), false); |
| EXPECT_STATUS(INetd::StatusCode::UNKNOWN_ERROR, retStatus); |
| EXPECT_EQ(0, countMatchingIpRules(regex1)); |
| EXPECT_EQ(0, countMatchingIpRules(regex2)); |
| } |
| |
| GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(NetdHidlTest); |
| INSTANTIATE_TEST_SUITE_P( |
| PerInstance, NetdHidlTest, |
| testing::ValuesIn(android::hardware::getAllHalInstanceNames(INetd::descriptor)), |
| android::hardware::PrintInstanceNameToString); |