blob: fe0ec2276d97a6013ea586208e7e134d6aaaec84 [file] [log] [blame] [edit]
// Copyright 2018 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "shill/network/throttler.h"
#include <memory>
#include <utility>
#include <base/functional/bind.h>
#include <base/test/task_environment.h>
#include <gmock/gmock.h>
#include "shill/network/mock_tc_process.h"
using testing::_;
using testing::Property;
using testing::WithArg;
namespace shill {
constexpr uint32_t kUploadThrottleRate = 100;
constexpr uint32_t kDownloadThrottleRate = 300;
const std::vector<std::string> kInterfaces = {"wlan0", "eth0"};
const char kNewAddedInterface[] = "ppp0";
const std::vector<std::string> kDisabledCommands = {
"qdisc del dev wlan0 root\n",
"qdisc del dev wlan0 ingress\n",
"qdisc del dev eth0 root\n",
"qdisc del dev eth0 ingress\n",
};
const std::vector<std::string> kEth0ThrottledCommands = {
"qdisc del dev eth0 root\n",
"qdisc del dev eth0 ingress\n",
"qdisc add dev eth0 root handle 1: htb default 11\n",
"class add dev eth0 parent 1: classid 1:1 htb rate 100kbit\n",
"class add dev eth0 parent 1:1 classid 1:11 htb rate 100kbit prio 0 "
"quantum 300\n",
"qdisc add dev eth0 handle ffff: ingress\n",
"filter add dev eth0 parent ffff: protocol all prio 50 u32 match ip src "
"0.0.0.0/0 police rate 300kbit burst 600k mtu 66000 drop flowid :1\n",
};
const std::vector<std::string> kWlan0ThrottledCommands = {
"qdisc del dev wlan0 root\n",
"qdisc del dev wlan0 ingress\n",
"qdisc add dev wlan0 root handle 1: htb default 11\n",
"class add dev wlan0 parent 1: classid 1:1 htb rate 100kbit\n",
"class add dev wlan0 parent 1:1 classid 1:11 htb rate 100kbit prio 0 "
"quantum 300\n",
"qdisc add dev wlan0 handle ffff: ingress\n",
"filter add dev wlan0 parent ffff: protocol all prio 50 u32 match ip src "
"0.0.0.0/0 police rate 300kbit burst 600k mtu 66000 drop flowid :1\n",
};
const std::vector<std::string> kPpp0ThrottledCommands = {
"qdisc del dev ppp0 root\n",
"qdisc del dev ppp0 ingress\n",
"qdisc add dev ppp0 root handle 1: htb default 11\n",
"class add dev ppp0 parent 1: classid 1:1 htb rate 100kbit\n",
"class add dev ppp0 parent 1:1 classid 1:11 htb rate 100kbit prio 0 "
"quantum 300\n",
"qdisc add dev ppp0 handle ffff: ingress\n",
"filter add dev ppp0 parent ffff: protocol all prio 50 u32 match ip src "
"0.0.0.0/0 police rate 300kbit burst 600k mtu 66000 drop flowid :1\n",
};
class Client {
public:
ResultCallback GetCallback() {
return base::BindOnce(&Client::OnThrottlerDone, base::Unretained(this));
}
MOCK_METHOD(void, OnThrottlerDone, (const Error&), ());
};
class ThrottlerTest : public testing::Test {
protected:
void SetUp() override {
auto tc_process_factory = std::make_unique<MockTCProcessFactory>();
tc_process_factory_ = tc_process_factory.get();
throttler_ = std::make_unique<Throttler>(std::move(tc_process_factory));
}
// Expects to create a TCProcess with the commands. The exit callback will be
// executed when task_environment_.RunUntilIdle().
void ExpectCreateTCProcess(const std::vector<std::string>& commands) {
EXPECT_CALL(*tc_process_factory_, Create(commands, _, _))
.WillOnce(WithArg<1>([this](TCProcess::ExitCallback exit_callback) {
task_environment_.GetMainThreadTaskRunner()->PostTask(
FROM_HERE, base::BindOnce(std::move(exit_callback), 0));
return std::make_unique<MockTCProcess>();
}));
}
base::test::TaskEnvironment task_environment_;
MockTCProcessFactory* tc_process_factory_;
std::unique_ptr<Throttler> throttler_;
};
TEST_F(ThrottlerTest, ThrottleInterfaces) {
ExpectCreateTCProcess(kEth0ThrottledCommands);
ExpectCreateTCProcess(kWlan0ThrottledCommands);
Client client;
EXPECT_CALL(client, OnThrottlerDone(Property(&Error::type, Error::kSuccess)));
throttler_->ThrottleInterfaces(client.GetCallback(), kUploadThrottleRate,
kDownloadThrottleRate, kInterfaces);
task_environment_.RunUntilIdle();
// After successfully throttling interfaces, ApplyThrottleToNewInterface()
// could throttle new interface with the same throttled rate.
ExpectCreateTCProcess(kPpp0ThrottledCommands);
EXPECT_TRUE(throttler_->ApplyThrottleToNewInterface(kNewAddedInterface));
task_environment_.RunUntilIdle();
}
TEST_F(ThrottlerTest, ThrottleInterfacesWithoutBitrate) {
Client client;
EXPECT_CALL(client, OnThrottlerDone(
Property(&Error::type, Error::kInvalidArguments)));
throttler_->ThrottleInterfaces(client.GetCallback(), 0, 0, kInterfaces);
}
TEST_F(ThrottlerTest, ApplyThrottleToNewInterface) {
ExpectCreateTCProcess(kEth0ThrottledCommands);
ExpectCreateTCProcess(kWlan0ThrottledCommands);
ExpectCreateTCProcess(kPpp0ThrottledCommands);
Client client;
EXPECT_CALL(client, OnThrottlerDone(Property(&Error::type, Error::kSuccess)));
throttler_->ThrottleInterfaces(client.GetCallback(), kUploadThrottleRate,
kDownloadThrottleRate, kInterfaces);
// Calling ApplyThrottleToNewInterface() before the previous
// ThrottleInterfaces() has been finished (simulated by RunUntilIdle()) should
// also work.
EXPECT_TRUE(throttler_->ApplyThrottleToNewInterface(kNewAddedInterface));
task_environment_.RunUntilIdle();
}
TEST_F(ThrottlerTest, ApplyThrottleToNewInterfaceWithoutThrottleRate) {
EXPECT_FALSE(throttler_->ApplyThrottleToNewInterface(kNewAddedInterface));
}
TEST_F(ThrottlerTest, DisableThrottlingOnAllInterfaces) {
ExpectCreateTCProcess(kDisabledCommands);
Client client;
EXPECT_CALL(client, OnThrottlerDone(Property(&Error::type, Error::kSuccess)));
throttler_->DisableThrottlingOnAllInterfaces(client.GetCallback(),
kInterfaces);
task_environment_.RunUntilIdle();
// ApplyThrottleToNewInterface() should fail after throttling is disabled.
EXPECT_FALSE(throttler_->ApplyThrottleToNewInterface(kNewAddedInterface));
}
TEST_F(ThrottlerTest, DisableDuringThrottling) {
// When DisableThrottlingOnAllInterfaces() is called before throttling the
// first interface is finished, then:
// - Throttler only creates a TC process to throttling the first interface,
// and another TC process to disable the throttling.
// - The result of ThrottleInterfaces() will be kOperationAborted, and the
// result of DisableThrottlingOnAllInterfaces is kSuccess.
ExpectCreateTCProcess(kEth0ThrottledCommands);
ExpectCreateTCProcess(kDisabledCommands);
Client client;
EXPECT_CALL(client, OnThrottlerDone(
Property(&Error::type, Error::kOperationAborted)));
EXPECT_CALL(client, OnThrottlerDone(Property(&Error::type, Error::kSuccess)));
throttler_->ThrottleInterfaces(client.GetCallback(), kUploadThrottleRate,
kDownloadThrottleRate, kInterfaces);
throttler_->DisableThrottlingOnAllInterfaces(client.GetCallback(),
kInterfaces);
task_environment_.RunUntilIdle();
}
TEST_F(ThrottlerTest, DisableThrottlingOnEmptyInterfaces) {
Client client;
EXPECT_CALL(client, OnThrottlerDone(Property(&Error::type, Error::kSuccess)));
throttler_->DisableThrottlingOnAllInterfaces(client.GetCallback(), {});
task_environment_.RunUntilIdle();
// ApplyThrottleToNewInterface() should fail after throttling is disabled.
EXPECT_FALSE(throttler_->ApplyThrottleToNewInterface(kNewAddedInterface));
}
} // namespace shill