Add test coverage for VPN local exclusion
Add more tests to verify the combination of uidrange, explicitly
selection and local/non-local destination in bypassable VPN.
Bug: 184750836
Test: cd system/netd ; atest
Change-Id: Ibd47718b4e64048f815bd49737e362e6360bcd98
(cherry picked from commit d73826d98cee2f3f89285c00cca374a9e25d6081)
Merged-In: Ibd47718b4e64048f815bd49737e362e6360bcd98
diff --git a/tests/binder_test.cpp b/tests/binder_test.cpp
index 364c10c..dae1af3 100644
--- a/tests/binder_test.cpp
+++ b/tests/binder_test.cpp
@@ -144,6 +144,7 @@
using android::net::netd::aidl::NativeUidRangeConfig;
using android::netdutils::getIfaceNames;
using android::netdutils::IPAddress;
+using android::netdutils::IPSockAddr;
using android::netdutils::ScopedAddrinfo;
using android::netdutils::sSyscalls;
using android::netdutils::Stopwatch;
@@ -243,6 +244,13 @@
int vpnNetId, bool secure,
std::vector<UidRangeParcel>&& appDefaultUidRanges,
std::vector<UidRangeParcel>&& vpnUidRanges);
+
+ void setupNetworkRoutesForVpnAndDefaultNetworks(
+ int systemDefaultNetId, int appDefaultNetId, int vpnNetId, int otherNetId, bool secure,
+ bool excludeLocalRoutes, bool testV6, bool differentLocalAddr,
+ std::vector<UidRangeParcel>&& appDefaultUidRanges,
+ std::vector<UidRangeParcel>&& vpnUidRanges);
+
protected:
// Use -1 to represent that default network was not modified because
// real netId must be an unsigned value.
@@ -3533,32 +3541,51 @@
EXPECT_GT(read(tunFd, buf, sizeof(buf)), 0);
}
+bool sendPacketFromUid(uid_t uid, IPSockAddr& dstAddr, Fwmark* fwmark, int tunFd,
+ bool doConnect = true) {
+ int family = dstAddr.family();
+ ScopedUidChange scopedUidChange(uid);
+ unique_fd testSocket(socket(family, SOCK_DGRAM | SOCK_CLOEXEC, 0));
+
+ if (testSocket < 0) return false;
+ const sockaddr_storage dst = IPSockAddr(dstAddr.ip(), dstAddr.port());
+ if (doConnect && connect(testSocket, (sockaddr*)&dst, sizeof(dst)) == -1) return false;
+
+ socklen_t fwmarkLen = sizeof(fwmark->intValue);
+ EXPECT_NE(-1, getsockopt(testSocket, SOL_SOCKET, SO_MARK, &(fwmark->intValue), &fwmarkLen));
+
+ int addr_len = (family == AF_INET) ? INET_ADDRSTRLEN : INET6_ADDRSTRLEN;
+ char addr[addr_len];
+ inet_ntop(family, &dstAddr, addr, addr_len);
+ SCOPED_TRACE(StringPrintf("sendPacket, addr: %s, uid: %u, doConnect: %s", addr, uid,
+ doConnect ? "true" : "false"));
+ if (doConnect) {
+ checkDataReceived(testSocket, tunFd, nullptr, 0);
+ } else {
+ checkDataReceived(testSocket, tunFd, (sockaddr*)&dst, sizeof(dst));
+ }
+
+ return true;
+}
+
+bool sendIPv4PacketFromUid(uid_t uid, const in_addr& dstAddr, Fwmark* fwmark, int tunFd,
+ bool doConnect = true) {
+ const sockaddr_in dst = {.sin_family = AF_INET, .sin_port = 42, .sin_addr = dstAddr};
+ IPSockAddr addr = IPSockAddr(dst);
+
+ return sendPacketFromUid(uid, addr, fwmark, tunFd, doConnect);
+}
+
bool sendIPv6PacketFromUid(uid_t uid, const in6_addr& dstAddr, Fwmark* fwmark, int tunFd,
bool doConnect = true) {
- ScopedUidChange scopedUidChange(uid);
- unique_fd testSocket(socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0));
- if (testSocket < 0) return false;
-
const sockaddr_in6 dst6 = {
.sin6_family = AF_INET6,
.sin6_port = 42,
.sin6_addr = dstAddr,
};
- if (doConnect && connect(testSocket, (sockaddr*)&dst6, sizeof(dst6)) == -1) return false;
+ IPSockAddr addr = IPSockAddr(dst6);
- socklen_t fwmarkLen = sizeof(fwmark->intValue);
- EXPECT_NE(-1, getsockopt(testSocket, SOL_SOCKET, SO_MARK, &(fwmark->intValue), &fwmarkLen));
-
- char addr[INET6_ADDRSTRLEN];
- inet_ntop(AF_INET6, &dstAddr, addr, INET6_ADDRSTRLEN);
- SCOPED_TRACE(StringPrintf("sendIPv6Packet, addr: %s, uid: %u, doConnect: %s", addr, uid,
- doConnect ? "true" : "false"));
- if (doConnect) {
- checkDataReceived(testSocket, tunFd, nullptr, 0);
- } else {
- checkDataReceived(testSocket, tunFd, (sockaddr*)&dst6, sizeof(dst6));
- }
- return true;
+ return sendPacketFromUid(uid, addr, fwmark, tunFd, doConnect);
}
// Send an IPv6 packet from the uid. Expect to fail and get specified errno.
@@ -3602,10 +3629,9 @@
}
void expectVpnFallthroughWorks(android::net::INetd* netdService, bool bypassable, uid_t uid,
- uid_t uidNotInVpn, const TunInterface& fallthroughNetwork,
+ const TunInterface& fallthroughNetwork,
const TunInterface& vpnNetwork, const TunInterface& otherNetwork,
- int vpnNetId = TEST_NETID2, int fallthroughNetId = TEST_NETID1,
- int otherNetId = TEST_NETID3) {
+ int vpnNetId = TEST_NETID2, int fallthroughNetId = TEST_NETID1) {
// Set default network to NETID_UNSET
EXPECT_TRUE(netdService->networkSetDefault(NETID_UNSET).isOk());
@@ -3682,25 +3708,6 @@
EXPECT_FALSE(sendIPv6PacketFromUid(uid, outsideVpnAddr, &fwmark, fallthroughFd));
EXPECT_FALSE(sendIPv6PacketFromUid(uid, insideVpnAddr, &fwmark, fallthroughFd));
}
-
- // Add per-app uid ranges.
- EXPECT_TRUE(netdService
- ->networkAddUidRanges(otherNetId,
- {makeUidRangeParcel(uidNotInVpn, uidNotInVpn)})
- .isOk());
-
- int appDefaultFd = otherNetwork.getFdForTesting();
-
- // UID is not inside the VPN range, so it won't go to vpn network.
- // It won't fall into per app local rule because it's explicitly selected.
- EXPECT_TRUE(sendIPv6PacketFromUid(uidNotInVpn, outsideVpnAddr, &fwmark, fallthroughFd));
- EXPECT_TRUE(sendIPv6PacketFromUid(uidNotInVpn, insideVpnAddr, &fwmark, fallthroughFd));
-
- // Reset explicitly selection.
- setNetworkForProcess(NETID_UNSET);
- // Connections can go to app default network.
- EXPECT_TRUE(sendIPv6PacketFromUid(uidNotInVpn, insideVpnAddr, &fwmark, appDefaultFd));
- EXPECT_TRUE(sendIPv6PacketFromUid(uidNotInVpn, outsideVpnAddr, &fwmark, appDefaultFd));
}
} // namespace
@@ -3709,16 +3716,14 @@
createVpnNetworkWithUid(true /* secure */, TEST_UID1);
// Get current default network NetId
ASSERT_TRUE(mNetd->networkGetDefault(&mStoredDefaultNetwork).isOk());
- expectVpnFallthroughWorks(mNetd.get(), false /* bypassable */, TEST_UID1, TEST_UID2, sTun,
- sTun2, sTun3);
+ expectVpnFallthroughWorks(mNetd.get(), false /* bypassable */, TEST_UID1, sTun, sTun2, sTun3);
}
TEST_F(NetdBinderTest, BypassableVPNFallthrough) {
createVpnNetworkWithUid(false /* secure */, TEST_UID1);
// Get current default network NetId
ASSERT_TRUE(mNetd->networkGetDefault(&mStoredDefaultNetwork).isOk());
- expectVpnFallthroughWorks(mNetd.get(), true /* bypassable */, TEST_UID1, TEST_UID2, sTun, sTun2,
- sTun3);
+ expectVpnFallthroughWorks(mNetd.get(), true /* bypassable */, TEST_UID1, sTun, sTun2, sTun3);
}
namespace {
@@ -4368,6 +4373,309 @@
expectPacketSentOnNetId(TEST_UID2, NETID_UNSET, vpnFd, UNCONNECTED_SOCKET);
}
+class VpnLocalRoutesParameterizedTest
+ : public NetdBinderTest,
+ public testing::WithParamInterface<std::tuple<int, int, bool, bool, bool, bool>> {
+ protected:
+ // Local/non-local addresses based on the route added above.
+ in_addr V4_LOCAL_ADDR = {htonl(0xC0A80008)}; // 192.168.0.8
+ in_addr V4_APP_LOCAL_ADDR = {htonl(0xAC100008)}; // 172.16.0.8
+ in_addr V4_GLOBAL_ADDR = {htonl(0x08080808)}; // 8.8.8.8
+
+ in6_addr V6_LOCAL_ADDR = {
+ {// 2001:db8:cafe::1
+ .u6_addr8 = {0x20, 0x01, 0x0d, 0xb8, 0xca, 0xfe, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}}};
+ in6_addr V6_APP_LOCAL_ADDR = {
+ {// 2607:f0d0:1234::4
+ .u6_addr8 = {0x26, 0x07, 0xf0, 0xd0, 0x12, 0x34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4}}};
+ in6_addr V6_GLOBAL_ADDR = {
+ {// 2607:1234:1002::4
+ .u6_addr8 = {0x26, 0x07, 0x12, 0x34, 0x10, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4}}};
+};
+
+const int SEND_TO_GLOBAL = 0;
+const int SEND_TO_SYSTEM_LOCAL = 1;
+const int SEND_TO_APP_LOCAL = 2;
+
+// Exercise the combination of different explicitly selected network, different uid, local/non-local
+// address on local route exclusion VPN. E.g.
+// explicitlySelected systemDefault + uid in VPN range + no app default + non local address
+// explicitlySelected systemDefault + uid in VPN range + has app default + non local address
+// explicitlySelected systemDefault + uid in VPN range + has app default + local address
+// explicitlySelected appDefault + uid not in VPN range + has app default + non local address
+INSTANTIATE_TEST_SUITE_P(
+ PerAppDefaultNetwork, VpnLocalRoutesParameterizedTest,
+ testing::Combine(testing::Values(SYSTEM_DEFAULT_NETID, APP_DEFAULT_NETID, NETID_UNSET),
+ testing::Values(SEND_TO_GLOBAL, SEND_TO_SYSTEM_LOCAL, SEND_TO_APP_LOCAL),
+ testing::Bool(), testing::Bool(), testing::Bool(), testing::Bool()),
+ [](const testing::TestParamInfo<std::tuple<int, int, bool, bool, bool, bool>>& info) {
+ std::string explicitlySelected;
+ switch (std::get<0>(info.param)) {
+ case SYSTEM_DEFAULT_NETID:
+ explicitlySelected = "explicitlySelectedSystemDefault";
+ break;
+ case APP_DEFAULT_NETID:
+ explicitlySelected = "explicitlySelectedAppDefault";
+ break;
+ case NETID_UNSET:
+ explicitlySelected = "implicitlySelected";
+ break;
+ default:
+ explicitlySelected = "InvalidParameter"; // Should not happen.
+ }
+
+ std::string sendToAddr;
+ switch (std::get<1>(info.param)) {
+ case SEND_TO_GLOBAL:
+ sendToAddr = "GlobalAddr";
+ break;
+ case SEND_TO_SYSTEM_LOCAL:
+ sendToAddr = "SystemLocal";
+ break;
+ case SEND_TO_APP_LOCAL:
+ sendToAddr = "AppLocal";
+ break;
+ default:
+ sendToAddr = "InvalidAddr"; // Should not happen.
+ }
+
+ const std::string isSubjectToVpn = std::get<2>(info.param)
+ ? std::string("SubjectToVpn")
+ : std::string("NotSubjectToVpn");
+
+ const std::string hasAppDefaultNetwork = std::get<3>(info.param)
+ ? std::string("HasAppDefault")
+ : std::string("NothasAppDefault");
+
+ const std::string testV6 =
+ std::get<4>(info.param) ? std::string("v6") : std::string("v4");
+
+ // Apply the same or different local address in app default and system default.
+ const std::string differentLocalAddr = std::get<5>(info.param)
+ ? std::string("DifferentLocalAddr")
+ : std::string("SameLocalAddr");
+
+ return explicitlySelected + "_uid" + isSubjectToVpn + hasAppDefaultNetwork +
+ "Range_with" + testV6 + sendToAddr + differentLocalAddr;
+ });
+
+int getTargetIfaceForLocalRoutesExclusion(bool isSubjectToVpn, bool hasAppDefaultNetwork,
+ bool differentLocalAddr, int sendToAddr,
+ int selectedNetId, int fallthroughFd, int appDefaultFd,
+ int vpnFd) {
+ int expectedIface;
+
+ // Setup the expected interface based on the condition.
+ if (isSubjectToVpn && hasAppDefaultNetwork) {
+ switch (sendToAddr) {
+ case SEND_TO_GLOBAL:
+ expectedIface = vpnFd;
+ break;
+ case SEND_TO_SYSTEM_LOCAL:
+ // Go to app default if the app default and system default are the same range
+ // TODO(b/237351736): It should go to VPN if the system local and app local are
+ // different.
+ expectedIface = differentLocalAddr ? fallthroughFd : appDefaultFd;
+ break;
+ case SEND_TO_APP_LOCAL:
+ expectedIface = appDefaultFd;
+ break;
+ default:
+ expectedIface = -1; // should not happen
+ }
+ } else if (isSubjectToVpn && !hasAppDefaultNetwork) {
+ switch (sendToAddr) {
+ case SEND_TO_GLOBAL:
+ expectedIface = vpnFd;
+ break;
+ case SEND_TO_SYSTEM_LOCAL:
+ // TODO(b/237351736): It should go to app default if the system local and app local
+ // are different.
+ expectedIface = fallthroughFd;
+ break;
+ case SEND_TO_APP_LOCAL:
+ // Go to system default if the system default and app default are the same range.
+ expectedIface = differentLocalAddr ? vpnFd : fallthroughFd;
+ break;
+ default:
+ expectedIface = -1; // should not happen
+ }
+ } else if (!isSubjectToVpn && hasAppDefaultNetwork) {
+ expectedIface = appDefaultFd;
+ } else { // !isVpnUidRange && !isAppDefaultRange
+ expectedIface = fallthroughFd;
+ }
+
+ // Override the target if it's explicitly selected.
+ switch (selectedNetId) {
+ case SYSTEM_DEFAULT_NETID:
+ expectedIface = fallthroughFd;
+ break;
+ case APP_DEFAULT_NETID:
+ expectedIface = appDefaultFd;
+ break;
+ default:
+ break;
+ // Based on the uid range.
+ }
+
+ return expectedIface;
+}
+
+// This routing configurations verify the worst case where both physical networks and vpn
+// network have the same local address.
+// This also set as system default routing for verifying different app default and system
+// default routing.
+std::vector<std::string> V6_ROUTES = {"2001:db8:cafe::/48", "::/0"};
+std::vector<std::string> V4_ROUTES = {"192.168.0.0/16", "0.0.0.0/0"};
+
+// Routing configuration used for verifying different app default and system default routing
+// configuration
+std::vector<std::string> V6_APP_DEFAULT_ROUTES = {"2607:f0d0:1234::/48", "::/0"};
+std::vector<std::string> V4_APP_DEFAULT_ROUTES = {"172.16.0.0/16", "0.0.0.0/0"};
+
+void NetdBinderTest::setupNetworkRoutesForVpnAndDefaultNetworks(
+ int systemDefaultNetId, int appDefaultNetId, int vpnNetId, int otherNetId, bool secure,
+ bool excludeLocalRoutes, bool testV6, bool differentLocalAddr,
+ std::vector<UidRangeParcel>&& appDefaultUidRanges,
+ std::vector<UidRangeParcel>&& vpnUidRanges) {
+ // Create a physical network on sTun, and set it as the system default network
+ createAndSetDefaultNetwork(systemDefaultNetId, sTun.name());
+
+ // Routes are configured to system default, app default and vpn network to verify if the packets
+ // are routed correctly.
+
+ // Setup system default routing.
+ std::vector<std::string> systemDefaultRoutes = testV6 ? V6_ROUTES : V4_ROUTES;
+ for (const auto& route : systemDefaultRoutes) {
+ EXPECT_TRUE(mNetd->networkAddRoute(systemDefaultNetId, sTun.name(), route, "").isOk());
+ }
+
+ // Create another physical network on sTun2 as per app default network
+ createPhysicalNetwork(appDefaultNetId, sTun2.name());
+
+ // Setup app default routing.
+ std::vector<std::string> appDefaultRoutes =
+ testV6 ? (differentLocalAddr ? V6_APP_DEFAULT_ROUTES : V6_ROUTES)
+ : (differentLocalAddr ? V4_APP_DEFAULT_ROUTES : V4_ROUTES);
+ for (const auto& route : appDefaultRoutes) {
+ EXPECT_TRUE(mNetd->networkAddRoute(appDefaultNetId, sTun2.name(), route, "").isOk());
+ }
+
+ // Create a bypassable VPN on sTun3.
+ auto config = makeNativeNetworkConfig(vpnNetId, NativeNetworkType::VIRTUAL,
+ INetd::PERMISSION_NONE, secure, excludeLocalRoutes);
+ EXPECT_TRUE(mNetd->networkCreate(config).isOk());
+ EXPECT_TRUE(mNetd->networkAddInterface(vpnNetId, sTun3.name()).isOk());
+
+ // Setup vpn routing.
+ std::vector<std::string> vpnRoutes = testV6 ? V6_ROUTES : V4_ROUTES;
+ for (const auto& route : vpnRoutes) {
+ EXPECT_TRUE(mNetd->networkAddRoute(vpnNetId, sTun3.name(), route, "").isOk());
+ }
+
+ // Create another interface that is neither system default nor the app default to make sure
+ // the traffic won't be mis-routed.
+ createPhysicalNetwork(otherNetId, sTun4.name());
+
+ // Add per-app uid ranges.
+ EXPECT_TRUE(mNetd->networkAddUidRanges(appDefaultNetId, appDefaultUidRanges).isOk());
+
+ // Add VPN uid ranges.
+ EXPECT_TRUE(mNetd->networkAddUidRanges(vpnNetId, vpnUidRanges).isOk());
+}
+
+// Routes are in approximately the following order for bypassable VPNs that allow local network
+// access:
+// - Per-app default local routes (UID guarded)
+// - System-wide default local routes
+// - VPN catch-all routes (UID guarded)
+// - Per-app default global routes (UID guarded)
+// - System-wide default global routes
+TEST_P(VpnLocalRoutesParameterizedTest, localRoutesExclusion) {
+ int selectedNetId;
+ int sendToAddr;
+ bool isSubjectToVpn;
+ bool hasAppDefaultNetwork;
+ bool testV6;
+ bool differentLocalAddr;
+
+ std::tie(selectedNetId, sendToAddr, isSubjectToVpn, hasAppDefaultNetwork, testV6,
+ differentLocalAddr) = GetParam();
+
+ // std::vector<std::string> routes = testV6 ? V6_ROUTES : V4_ROUTES;
+ setupNetworkRoutesForVpnAndDefaultNetworks(
+ SYSTEM_DEFAULT_NETID, APP_DEFAULT_NETID, VPN_NETID, TEST_NETID4, false /* secure */,
+ true /* excludeLocalRoutes */, testV6,
+ // Add a local route first to setup local table.
+ differentLocalAddr, {makeUidRangeParcel(TEST_UID2, TEST_UID1)},
+ {makeUidRangeParcel(TEST_UID3, TEST_UID2)});
+
+ int fallthroughFd = sTun.getFdForTesting();
+ int appDefaultFd = sTun2.getFdForTesting();
+ int vpnFd = sTun3.getFdForTesting();
+
+ // Explicitly select network
+ setNetworkForProcess(selectedNetId);
+
+ int targetUid;
+
+ // Setup the expected testing uid
+ if (isSubjectToVpn && hasAppDefaultNetwork) {
+ targetUid = TEST_UID2;
+ } else if (isSubjectToVpn && !hasAppDefaultNetwork) {
+ targetUid = TEST_UID3;
+ } else if (!isSubjectToVpn && hasAppDefaultNetwork) {
+ targetUid = TEST_UID1;
+ } else {
+ targetUid = AID_ROOT;
+ }
+
+ // Get target interface for the traffic.
+ int targetIface = getTargetIfaceForLocalRoutesExclusion(
+ isSubjectToVpn, hasAppDefaultNetwork, differentLocalAddr, sendToAddr, selectedNetId,
+ fallthroughFd, appDefaultFd, vpnFd);
+
+ // Verify the packets are sent to the expected interface.
+ Fwmark fwmark;
+ if (testV6) {
+ in6_addr addr;
+ switch (sendToAddr) {
+ case SEND_TO_GLOBAL:
+ addr = V6_GLOBAL_ADDR;
+ break;
+ case SEND_TO_SYSTEM_LOCAL:
+ addr = V6_LOCAL_ADDR;
+ break;
+ case SEND_TO_APP_LOCAL:
+ addr = differentLocalAddr ? V6_APP_LOCAL_ADDR : V6_LOCAL_ADDR;
+ break;
+ default:
+ break;
+ // should not happen
+ }
+ EXPECT_TRUE(sendIPv6PacketFromUid(targetUid, addr, &fwmark, targetIface));
+ } else {
+ in_addr addr;
+ switch (sendToAddr) {
+ case SEND_TO_GLOBAL:
+ addr = V4_GLOBAL_ADDR;
+ break;
+ case SEND_TO_SYSTEM_LOCAL:
+ addr = V4_LOCAL_ADDR;
+ break;
+ case SEND_TO_APP_LOCAL:
+ addr = differentLocalAddr ? V4_APP_LOCAL_ADDR : V4_LOCAL_ADDR;
+ break;
+ default:
+ break;
+ // should not happen
+ }
+
+ EXPECT_TRUE(sendIPv4PacketFromUid(targetUid, addr, &fwmark, targetIface));
+ }
+}
+
TEST_F(NetdBinderTest, NetworkCreate) {
auto config = makeNativeNetworkConfig(TEST_NETID1, NativeNetworkType::PHYSICAL,
INetd::PERMISSION_NONE, false, false);