summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2023-09-13 01:27:54 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2023-09-13 01:27:54 +0000
commit750bda7c9b6400826dc5ce5e202b8e9ada15ef69 (patch)
tree3e0247c27a61816589a6de5e72ff4f2d6779f5c5
parent47e2027b2fa33b08e441214baac9a9f5227c21c7 (diff)
parent293f0d7686958df6621e82bd6d4486b9a058f20e (diff)
downloadnetd-android14-d2-s1-release.tar.gz
Change-Id: I0f31d690b7c636ed80697b5f3b4d61e5afc34751
-rw-r--r--client/NetdClient.cpp14
-rw-r--r--include/FwmarkCommand.h2
-rw-r--r--server/FwmarkServer.cpp9
-rw-r--r--server/NetworkController.cpp13
-rw-r--r--server/NetworkController.h2
-rw-r--r--tests/binder_test.cpp168
6 files changed, 193 insertions, 15 deletions
diff --git a/client/NetdClient.cpp b/client/NetdClient.cpp
index 2a09a440..fe0a4838 100644
--- a/client/NetdClient.cpp
+++ b/client/NetdClient.cpp
@@ -90,11 +90,6 @@ static bool propertyValueIsTrue(const char* prop_name) {
return false;
}
-static bool redirectSocketCallsIsTrue() {
- static bool cached_result = propertyValueIsTrue(PROPERTY_REDIRECT_SOCKET_CALLS);
- return cached_result;
-}
-
int checkSocket(int socketFd) {
if (socketFd < 0) {
return -EBADF;
@@ -150,13 +145,8 @@ int netdClientConnect(int sockfd, const sockaddr* addr, socklen_t addrlen) {
const bool shouldSetFwmark = shouldMarkSocket(sockfd, addr);
if (shouldSetFwmark) {
FwmarkCommand command = {FwmarkCommand::ON_CONNECT, 0, 0, 0};
- int error;
- if (redirectSocketCallsIsTrue()) {
- FwmarkConnectInfo connectInfo(0, 0, addr);
- error = FwmarkClient().send(&command, sockfd, &connectInfo);
- } else {
- error = FwmarkClient().send(&command, sockfd, nullptr);
- }
+ FwmarkConnectInfo connectInfo(0, 0, addr);
+ int error = FwmarkClient().send(&command, sockfd, &connectInfo);
if (error) {
errno = -error;
diff --git a/include/FwmarkCommand.h b/include/FwmarkCommand.h
index c215ed44..bf9d5063 100644
--- a/include/FwmarkCommand.h
+++ b/include/FwmarkCommand.h
@@ -29,7 +29,7 @@ struct FwmarkConnectInfo {
sockaddr s;
sockaddr_in sin;
sockaddr_in6 sin6;
- } addr;
+ } addr = {};
FwmarkConnectInfo() : error(0), latencyMs(0) {}
diff --git a/server/FwmarkServer.cpp b/server/FwmarkServer.cpp
index 1ae8ed38..4ce22eb5 100644
--- a/server/FwmarkServer.cpp
+++ b/server/FwmarkServer.cpp
@@ -16,6 +16,7 @@
#include "FwmarkServer.h"
+#include <net/if.h>
#include <netinet/in.h>
#include <selinux/selinux.h>
#include <sys/socket.h>
@@ -92,7 +93,7 @@ static bool hasDestinationAddress(FwmarkCommand::CmdId cmdId, bool redirectSocke
cmdId == FwmarkCommand::ON_SENDMSG || cmdId == FwmarkCommand::ON_SENDMMSG ||
cmdId == FwmarkCommand::ON_CONNECT_COMPLETE);
} else {
- return (cmdId == FwmarkCommand::ON_CONNECT_COMPLETE);
+ return (cmdId == FwmarkCommand::ON_CONNECT || cmdId == FwmarkCommand::ON_CONNECT_COMPLETE);
}
}
@@ -200,7 +201,11 @@ int FwmarkServer::processClient(SocketClient* client, int* socketFd) {
// So, overall (when the explicit bit is not set but the protect bit is set), if the
// existing NetId is a VPN, don't reset it. Else, set the default network's NetId.
if (!fwmark.explicitlySelected) {
- if (!fwmark.protectedFromVpn) {
+ if (family == AF_INET6 && connectInfo.addr.sin6.sin6_scope_id &&
+ IN6_IS_ADDR_LINKLOCAL(&connectInfo.addr.sin6.sin6_addr)) {
+ fwmark.netId = mNetworkController->getNetworkForInterface(
+ connectInfo.addr.sin6.sin6_scope_id);
+ } else if (!fwmark.protectedFromVpn) {
fwmark.netId = mNetworkController->getNetworkForConnect(client->getUid());
} else if (!mNetworkController->isVirtualNetwork(fwmark.netId)) {
fwmark.netId = mNetworkController->getDefaultNetwork();
diff --git a/server/NetworkController.cpp b/server/NetworkController.cpp
index e61ee306..d9b33426 100644
--- a/server/NetworkController.cpp
+++ b/server/NetworkController.cpp
@@ -373,6 +373,19 @@ unsigned NetworkController::getNetworkForInterface(const char* interface) const
return getNetworkForInterfaceLocked(interface);
}
+unsigned NetworkController::getNetworkForInterfaceLocked(const int ifIndex) const {
+ char interfaceName[IFNAMSIZ] = {};
+ if (if_indextoname(ifIndex, interfaceName)) {
+ return getNetworkForInterfaceLocked(interfaceName);
+ }
+ return NETID_UNSET;
+}
+
+unsigned NetworkController::getNetworkForInterface(const int ifIndex) const {
+ ScopedRLock lock(mRWLock);
+ return getNetworkForInterfaceLocked(ifIndex);
+}
+
bool NetworkController::isVirtualNetwork(unsigned netId) const {
ScopedRLock lock(mRWLock);
return isVirtualNetworkLocked(netId);
diff --git a/server/NetworkController.h b/server/NetworkController.h
index e7c47da4..a9a6cd4f 100644
--- a/server/NetworkController.h
+++ b/server/NetworkController.h
@@ -102,6 +102,7 @@ public:
unsigned getNetworkForConnect(uid_t uid) const;
void getNetworkContext(unsigned netId, uid_t uid, struct android_net_context* netcontext) const;
unsigned getNetworkForInterface(const char* interface) const;
+ unsigned getNetworkForInterface(const int ifIndex) const;
bool isVirtualNetwork(unsigned netId) const;
[[nodiscard]] int createPhysicalNetwork(unsigned netId, Permission permission, bool local);
@@ -160,6 +161,7 @@ public:
uint32_t getNetworkForDnsLocked(unsigned* netId, uid_t uid) const;
unsigned getNetworkForConnectLocked(uid_t uid) const;
unsigned getNetworkForInterfaceLocked(const char* interface) const;
+ unsigned getNetworkForInterfaceLocked(const int ifIndex) const;
bool canProtectLocked(uid_t uid) const;
bool isVirtualNetworkLocked(unsigned netId) const;
VirtualNetwork* getVirtualNetworkForUserLocked(uid_t uid) const;
diff --git a/tests/binder_test.cpp b/tests/binder_test.cpp
index 7d3bbc87..065bc126 100644
--- a/tests/binder_test.cpp
+++ b/tests/binder_test.cpp
@@ -118,6 +118,7 @@ using android::net::MarkMaskParcel;
using android::net::NativeNetworkConfig;
using android::net::NativeNetworkType;
using android::net::NativeVpnType;
+using android::net::NetworkController;
using android::net::RULE_PRIORITY_BYPASSABLE_VPN_LOCAL_EXCLUSION;
using android::net::RULE_PRIORITY_BYPASSABLE_VPN_NO_LOCAL_EXCLUSION;
using android::net::RULE_PRIORITY_DEFAULT_NETWORK;
@@ -5575,4 +5576,171 @@ TEST_F(NetdBinderTest, PerProfileNetworkPermission) {
EXPECT_EQ(0, setNetworkForProcess(ENTERPRISE_NETID_1));
}
}
+}
+
+namespace {
+
+class ScopedIfaceRouteOperation {
+ using IfaceCmd = std::tuple<int32_t, const std::string>;
+ using RouteCmd = std::tuple<int32_t, const std::string, const std::string, const std::string>;
+
+ // Helper type for the visitor.
+ template <class... Ts>
+ struct overloaded : Ts... {
+ using Ts::operator()...;
+ };
+ // Explicit deduction guide
+ template <class... Ts>
+ overloaded(Ts...) -> overloaded<Ts...>;
+
+ public:
+ ScopedIfaceRouteOperation(sp<INetd> netd) : mNetd(netd) {}
+
+ binder::Status addInterface(int32_t netId, const std::string& iface) {
+ const binder::Status status = mNetd->networkAddInterface(netId, iface);
+ if (status.isOk()) {
+ mCmds.push_back(std::make_tuple(netId, iface));
+ }
+ return status;
+ }
+
+ binder::Status addRoute(int32_t netId, const std::string& iface, const std::string& destination,
+ const std::string& nextHop) {
+ const binder::Status status = mNetd->networkAddRoute(netId, iface, destination, nextHop);
+ if (status.isOk()) {
+ mCmds.push_back(std::make_tuple(netId, iface, destination, nextHop));
+ }
+ return status;
+ }
+
+ ~ScopedIfaceRouteOperation() {
+ // Remove routes and interfaces in reverse order.
+ for (std::vector<std::variant<IfaceCmd, RouteCmd>>::reverse_iterator iter = mCmds.rbegin();
+ iter != mCmds.rend(); iter++) {
+ // Do corresponding works according to the type of the command pointed by the iter.
+ std::visit(overloaded{
+ [&](IfaceCmd& cmd) {
+ mNetd->networkRemoveInterface(std::get<0>(cmd),
+ std::get<1>(cmd));
+ },
+ [&](RouteCmd& cmd) {
+ mNetd->networkRemoveRoute(std::get<0>(cmd), std::get<1>(cmd),
+ std::get<2>(cmd), std::get<3>(cmd));
+ },
+ },
+ *iter);
+ }
+ }
+
+ private:
+ sp<INetd> mNetd;
+ std::vector<std::variant<IfaceCmd, RouteCmd>> mCmds;
+};
+
+std::optional<sockaddr_in6> getV6LinkLocalAddrFromIfIndex(const unsigned ifIndex) {
+ struct ifaddrs* ifAddrList = nullptr;
+ sockaddr_in6 linkLocalAddr{};
+
+ if (getifaddrs(&ifAddrList) == -1) return std::nullopt;
+
+ for (struct ifaddrs* ifa = ifAddrList; ifa != nullptr; ifa = ifa->ifa_next) {
+ if (ifa->ifa_addr != nullptr && ifa->ifa_addr->sa_family == AF_INET6) {
+ struct sockaddr_in6* addr = reinterpret_cast<struct sockaddr_in6*>(ifa->ifa_addr);
+ if (addr->sin6_scope_id == ifIndex && IN6_IS_ADDR_LINKLOCAL(&(addr->sin6_addr))) {
+ linkLocalAddr = *addr;
+ freeifaddrs(ifAddrList);
+ return linkLocalAddr;
+ }
+ }
+ }
+
+ freeifaddrs(ifAddrList);
+ return std::nullopt;
+}
+
+int retry_bind(int sockfd, struct sockaddr* addr, socklen_t addrlen) {
+ int ret = 0;
+
+ for (int retry = 0; retry < 10; retry++) {
+ ret = bind(sockfd, addr, addrlen);
+ if (ret == 0 || (ret == -1 && errno != EADDRNOTAVAIL)) {
+ break;
+ }
+ usleep(100 * 1000);
+ }
+ return ret;
+}
+
+} // namespace
+
+TEST_F(NetdBinderTest, V6LinkLocalFwmark) {
+ createAndSetDefaultNetwork(TEST_NETID1, sTun.name());
+
+ // Add an interface and route for Local network.
+ ScopedIfaceRouteOperation scopedOperation(mNetd);
+ EXPECT_TRUE(scopedOperation.addInterface(NetworkController::LOCAL_NET_ID, sTun2.name()).isOk());
+ EXPECT_TRUE(
+ scopedOperation.addRoute(NetworkController::LOCAL_NET_ID, sTun2.name(), "fe80::/64", "")
+ .isOk());
+
+ // Bind a listening socket to the auto assigned link-local address of the Local network.
+ std::optional<sockaddr_in6> v6LinkLocalAddr_1 = getV6LinkLocalAddrFromIfIndex(sTun2.ifindex());
+ ASSERT_TRUE(v6LinkLocalAddr_1.has_value()) << "errno:" << errno;
+ socklen_t len = sizeof(v6LinkLocalAddr_1.value());
+ unique_fd s1(socket(AF_INET6, SOCK_STREAM | SOCK_NONBLOCK, 0));
+ ASSERT_EQ(0, bind(s1, reinterpret_cast<sockaddr*>(&v6LinkLocalAddr_1.value()), len))
+ << "errno:" << errno;
+ ASSERT_EQ(0, getsockname(s1, reinterpret_cast<sockaddr*>(&v6LinkLocalAddr_1.value()), &len))
+ << "errno:" << errno;
+ ASSERT_EQ(0, listen(s1, 10)) << "errno:" << errno;
+
+ // Add another v6 link-local address.
+ const char* v6LinkLocalAddr_2 = "fe80::ace:d00d";
+ EXPECT_TRUE(mNetd->interfaceAddAddress(sTun2.name(), v6LinkLocalAddr_2, 64).isOk());
+
+ // Bind a client socket on the new added link-local address and connect it to the listen socket.
+ // Have different src and dst addresses is needed because we want to test the behavior of fwmark
+ // and destroying sockets. The same src and dst addresses are treated as loopbacks and won't be
+ // destroyed in any way.
+ const struct addrinfo hints = {
+ .ai_family = AF_INET6,
+ .ai_socktype = SOCK_STREAM,
+ .ai_flags = AI_NUMERICHOST,
+ };
+ struct addrinfo* addrinfoList = nullptr;
+ int ret = getaddrinfo(v6LinkLocalAddr_2, nullptr, &hints, &addrinfoList);
+ ScopedAddrinfo addrinfoCleanup(addrinfoList);
+ ASSERT_EQ(0, ret) << "errno:" << errno;
+
+ len = addrinfoList[0].ai_addrlen;
+ sockaddr_in6 sin6 = *reinterpret_cast<sockaddr_in6*>(addrinfoList[0].ai_addr);
+ sin6.sin6_scope_id = sTun2.ifindex();
+
+ unique_fd c1(socket(AF_INET6, SOCK_STREAM, 0));
+ // Retry in case the newly added address is not ready yet.
+ ASSERT_EQ(0, retry_bind(c1, reinterpret_cast<sockaddr*>(&sin6), len)) << "errno:" << errno;
+ ASSERT_EQ(0, getsockname(c1, reinterpret_cast<sockaddr*>(&sin6), &len)) << "errno:" << errno;
+ ASSERT_EQ(0, connect(c1, reinterpret_cast<sockaddr*>(&v6LinkLocalAddr_1.value()), len))
+ << "errno:" << errno;
+
+ // Verify netId in fwmark.
+ Fwmark fwmark;
+ socklen_t fwmarkLen = sizeof(fwmark.intValue);
+ EXPECT_EQ(0, getsockopt(c1, SOL_SOCKET, SO_MARK, &fwmark.intValue, &fwmarkLen));
+ EXPECT_EQ((unsigned)NetworkController::LOCAL_NET_ID, fwmark.netId);
+
+ unique_fd a1(accept(s1, nullptr, 0));
+ ASSERT_NE(-1, a1) << "errno:" << errno;
+ EXPECT_EQ(0, getsockopt(a1, SOL_SOCKET, SO_MARK, &fwmark.intValue, &fwmarkLen));
+ // TODO: Fix fwmark on the accept socket?
+ fwmark.netId = NetworkController::LOCAL_NET_ID;
+ EXPECT_EQ(0, setsockopt(a1, SOL_SOCKET, SO_MARK, &fwmark.intValue, sizeof(fwmark.intValue)));
+
+ // Change permission on the default network. Client socket should not be destroyed.
+ EXPECT_TRUE(
+ mNetd->networkSetPermissionForNetwork(TEST_NETID1, INetd::PERMISSION_NETWORK).isOk());
+
+ char buf[1024] = {};
+ EXPECT_EQ(3, write(a1, "foo", 3)) << "errno:" << errno;
+ EXPECT_EQ(3, read(c1, buf, sizeof(buf))) << "errno:" << errno;
} \ No newline at end of file