blob: 249bdfbe5718d6cb439c45c4d51356eae6aef993 [file] [log] [blame]
//
// Copyright (C) 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.
//
#include <errno.h>
#include <inttypes.h>
#include <limits.h>
#include <linux/inet_diag.h>
#include <linux/netlink.h>
#include <linux/sock_diag.h>
#include <linux/unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <gtest/gtest.h>
#include <cutils/qtaguid.h>
#include <netdutils/Misc.h>
#include <netdutils/Syscalls.h>
#include "NetlinkListener.h"
#include "TrafficController.h"
#include "bpf/BpfMap.h"
#include "bpf/BpfUtils.h"
#include "netdutils/Netlink.h"
// A test uid that is large enough so normal apps are not likely to take,
constexpr uid_t TEST_UID = UID_MAX - 2;
// A test tag arbitrarily selected.
constexpr uint32_t TEST_TAG = 0xFF0F0F0F;
constexpr uint32_t SOCK_CLOSE_WAIT_US = 30 * 1000;
constexpr uint32_t ENOBUFS_POLL_WAIT_US = 10 * 1000;
using android::base::Result;
using android::base::ResultError;
// This test set up a SkDestroyListener that is runing parallel with the production
// SkDestroyListener. The test will create thousands of sockets and tag them on the
// production cookieUidTagMap and close them in a short time. When the number of
// sockets get closed exceeds the buffer size, it will start to return ENOBUFF
// error. The error will be ignored by the production SkDestroyListener and the
// test will clean up the tags in tearDown if there is any remains.
// TODO: Instead of test the ENOBUFF error, we can test the production
// SkDestroyListener to see if it failed to delete a tagged socket when ENOBUFF
// triggerred.
class NetlinkListenerTest : public testing::Test {
protected:
NetlinkListenerTest() {}
BpfMap<uint64_t, UidTagValue> mCookieTagMap;
void SetUp() {
mCookieTagMap.reset(android::bpf::mapRetrieveRW(COOKIE_TAG_MAP_PATH));
ASSERT_TRUE(mCookieTagMap.isValid());
}
void TearDown() {
const auto deleteTestCookieEntries = [](const uint64_t& key, const UidTagValue& value,
BpfMap<uint64_t, UidTagValue>& map) {
if ((value.uid == TEST_UID) && (value.tag == TEST_TAG)) {
Result<void> res = map.deleteValue(key);
if (res.ok() || (res.error().code() == ENOENT)) {
return Result<void>();
}
ALOGE("Failed to delete data(cookie = %" PRIu64 "): %s\n", key,
strerror(res.error().code()));
}
// Move forward to next cookie in the map.
return Result<void>();
};
EXPECT_RESULT_OK(mCookieTagMap.iterateWithValue(deleteTestCookieEntries));
}
Result<void> checkNoGarbageTagsExist() {
const auto checkGarbageTags = [](const uint64_t&, const UidTagValue& value,
const BpfMap<uint64_t, UidTagValue>&) -> Result<void> {
if ((TEST_UID == value.uid) && (TEST_TAG == value.tag)) {
return ResultError("Closed socket is not untagged", EUCLEAN);
}
return Result<void>();
};
return mCookieTagMap.iterateWithValue(checkGarbageTags);
}
bool checkMassiveSocketDestroy(int totalNumber, bool expectError) {
std::unique_ptr<android::net::NetlinkListenerInterface> skDestroyListener;
auto result = android::net::TrafficController::makeSkDestroyListener();
if (!isOk(result)) {
ALOGE("Unable to create SkDestroyListener: %s", toString(result).c_str());
} else {
skDestroyListener = std::move(result.value());
}
int rxErrorCount = 0;
// Rx handler extracts nfgenmsg looks up and invokes registered dispatch function.
const auto rxErrorHandler = [&rxErrorCount](const int, const int) { rxErrorCount++; };
skDestroyListener->registerSkErrorHandler(rxErrorHandler);
int fds[totalNumber];
for (int i = 0; i < totalNumber; i++) {
fds[i] = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
// The likely reason for a failure is running out of available file descriptors.
EXPECT_LE(0, fds[i]) << i << " of " << totalNumber;
if (fds[i] < 0) {
// EXPECT_LE already failed above, so test case is a failure, but we don't
// want potentially tens of thousands of extra failures creating and then
// closing all these fds cluttering up the logs.
totalNumber = i;
break;
};
qtaguid_tagSocket(fds[i], TEST_TAG, TEST_UID);
}
// TODO: Use a separate thread that has it's own fd table so we can
// close sockets even faster simply by terminating that thread.
for (int i = 0; i < totalNumber; i++) {
EXPECT_EQ(0, close(fds[i]));
}
// wait a bit for netlink listener to handle all the messages.
usleep(SOCK_CLOSE_WAIT_US);
if (expectError) {
// If ENOBUFS triggered, check it only called into the handler once, ie.
// that the netlink handler is not spinning.
int currentErrorCount = rxErrorCount;
// 0 error count is acceptable because the system has chances to close all sockets
// normally.
EXPECT_LE(0, rxErrorCount);
if (!rxErrorCount) return true;
usleep(ENOBUFS_POLL_WAIT_US);
EXPECT_EQ(currentErrorCount, rxErrorCount);
} else {
EXPECT_RESULT_OK(checkNoGarbageTagsExist());
EXPECT_EQ(0, rxErrorCount);
}
return false;
}
};
TEST_F(NetlinkListenerTest, TestAllSocketUntagged) {
checkMassiveSocketDestroy(10, false);
checkMassiveSocketDestroy(100, false);
}
// Disabled because flaky on blueline-userdebug; this test relies on the main thread
// winning a race against the NetlinkListener::run() thread. There's no way to ensure
// things will be scheduled the same way across all architectures and test environments.
TEST_F(NetlinkListenerTest, DISABLED_TestSkDestroyError) {
bool needRetry = false;
int retryCount = 0;
do {
needRetry = checkMassiveSocketDestroy(32500, true);
if (needRetry) retryCount++;
} while (needRetry && retryCount < 3);
// Should review test if it can always close all sockets correctly.
EXPECT_GT(3, retryCount);
}