blob: eb4f29ac8b1547a351742499b50abe9d7a741618 [file] [log] [blame]
// Copyright 2023 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "patchpanel/qos_service.h"
#include <cstddef>
#include <memory>
#include <string_view>
#include <utility>
#include <vector>
#include <base/containers/span.h>
#include <base/memory/ptr_util.h>
#include <base/memory/weak_ptr.h>
#include <base/test/task_environment.h>
#include <chromeos/net-base/dns_client.h>
#include <chromeos/net-base/ipv4_address.h>
#include <chromeos/net-base/technology.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "patchpanel/connmark_updater.h"
#include "patchpanel/fake_process_runner.h"
#include "patchpanel/mock_connmark_updater.h"
#include "patchpanel/mock_conntrack_monitor.h"
#include "patchpanel/mock_datapath.h"
#include "patchpanel/noop_system.h"
#include "patchpanel/routing_service.h"
#include "patchpanel/shill_client.h"
namespace patchpanel {
namespace {
using ::testing::_;
using ::testing::ElementsAreArray;
using ::testing::Eq;
using ::testing::Mock;
using ::testing::NiceMock;
using ::testing::Return;
using ::testing::StrEq;
using DNSClient = net_base::DNSClient;
using IPAddress = net_base::IPAddress;
constexpr char kIPAddress1[] = "8.8.8.8";
constexpr char kIPAddress2[] = "8.8.8.4";
constexpr int kPort1 = 10000;
constexpr int kPort2 = 20000;
const net_base::IPAddress kIPv4DNS =
net_base::IPAddress::CreateFromString("8.8.8.8").value();
const net_base::IPAddress kIPv6DNS =
net_base::IPAddress::CreateFromString("fd00::53").value();
std::unique_ptr<patchpanel::SocketConnectionEvent>
CreateOpenSocketConnectionEvent() {
std::unique_ptr<patchpanel::SocketConnectionEvent> msg =
std::make_unique<patchpanel::SocketConnectionEvent>();
net_base::IPv4Address src_addr =
*net_base::IPv4Address::CreateFromString(kIPAddress1);
msg->set_saddr(src_addr.ToByteString());
net_base::IPv4Address dst_addr =
*net_base::IPv4Address::CreateFromString(kIPAddress2);
msg->set_daddr(dst_addr.ToByteString());
msg->set_sport(kPort1);
msg->set_dport(kPort2);
msg->set_proto(patchpanel::SocketConnectionEvent::IpProtocol::
SocketConnectionEvent_IpProtocol_TCP);
msg->set_category(patchpanel::SocketConnectionEvent::QosCategory::
SocketConnectionEvent_QosCategory_REALTIME_INTERACTIVE);
msg->set_event(patchpanel::SocketConnectionEvent::SocketEvent::
SocketConnectionEvent_SocketEvent_OPEN);
return msg;
}
std::unique_ptr<patchpanel::SocketConnectionEvent>
CreateCloseSocketConnectionEvent() {
std::unique_ptr<patchpanel::SocketConnectionEvent> msg =
CreateOpenSocketConnectionEvent();
msg->set_event(patchpanel::SocketConnectionEvent::SocketEvent::
SocketConnectionEvent_SocketEvent_CLOSE);
return msg;
}
// The fake client doesn't need to do anything. WeakPtrFactory is for querying
// whether the object is still valid in the test.
class FakeDNSClient : public DNSClient {
public:
base::WeakPtr<FakeDNSClient> AsWeakPtr() {
return weak_factory_.GetWeakPtr();
}
private:
base::WeakPtrFactory<FakeDNSClient> weak_factory_{this};
};
class FakeDNSClientFactory : public net_base::DNSClientFactory {
public:
using Callbacks = std::vector<DNSClient::Callback>;
FakeDNSClientFactory() {
ON_CALL(*this, Resolve)
.WillByDefault([this](net_base::IPFamily family,
std::string_view hostname,
DNSClient::Callback callback,
const DNSClient::Options& options,
net_base::AresInterface* ares_interface) {
switch (family) {
case net_base::IPFamily::kIPv4:
ipv4_callbacks_.emplace_back(std::move(callback));
break;
case net_base::IPFamily::kIPv6:
ipv6_callbacks_.emplace_back(std::move(callback));
break;
}
auto ret = std::make_unique<FakeDNSClient>();
clients_.push_back(ret->AsWeakPtr());
return ret;
});
}
MOCK_METHOD(std::unique_ptr<DNSClient>,
Resolve,
(net_base::IPFamily family,
std::string_view hostname,
DNSClient::Callback callback,
const DNSClient::Options& options,
net_base::AresInterface* ares_interface),
(override));
void TriggerIPv4Callback(const DNSClient::Result& result) {
std::move(ipv4_callbacks_.back()).Run(result);
ipv4_callbacks_.pop_back();
}
void TriggerIPv6Callback(const DNSClient::Result& result) {
std::move(ipv6_callbacks_.back()).Run(result);
ipv6_callbacks_.pop_back();
}
// Returns a copy of weak pointers to existing clients.
std::vector<base::WeakPtr<FakeDNSClient>> GetWeakPtrsToExistingClients()
const {
return clients_;
}
const Callbacks& ipv4_callbacks() const { return ipv4_callbacks_; }
const Callbacks& ipv6_callbacks() const { return ipv6_callbacks_; }
private:
std::vector<DNSClient::Callback> ipv4_callbacks_;
std::vector<DNSClient::Callback> ipv6_callbacks_;
std::vector<base::WeakPtr<FakeDNSClient>> clients_;
};
// Notes:
// - ShillClient is only used for accessing states in QoSService so fake is
// better than mock in this test;
// - The FakeShillClient in fake_shill_client.h is too complicated to use so
// create our own one here. Add a suffix in the name to avoid conflicts in any
// case.
class FakeShillClientForQoS : public ShillClient {
public:
FakeShillClientForQoS() : ShillClient(nullptr, nullptr) {}
~FakeShillClientForQoS() = default;
const Device* GetDeviceByShillDeviceName(
const std::string& shill_device_interface_property) const override {
for (const auto& device : devices_) {
if (device.ifname == shill_device_interface_property) {
return &device;
}
}
return nullptr;
}
void set_devices(const std::vector<Device>& devices) { devices_ = devices; }
void set_doh_providers(const DoHProviders& value) {
set_doh_providers_for_testing(value);
}
private:
std::vector<Device> devices_;
};
ShillClient::Device CreateShillDevice(
std::string_view ifname,
bool is_connected,
const std::vector<net_base::IPAddress> dns_servers) {
ShillClient::Device device;
device.ifname = ifname;
device.technology = net_base::Technology::kWiFi;
device.network_config.dns_servers = dns_servers;
if (is_connected) {
// The value does not matter, just to make sure Device::IsConnected()
// returns true.
device.network_config.ipv4_address =
net_base::IPv4CIDR::CreateFromCIDRString("1.2.3.4/32").value();
}
return device;
}
class QoSServiceTest : public testing::Test {
protected:
QoSServiceTest()
: datapath_(&process_runner_, &system_),
dns_factory_(new FakeDNSClientFactory()),
qos_svc_(&datapath_,
&conntrack_monitor_,
&shill_client_,
base::WrapUnique(dns_factory_)) {}
// Note that this needs to be initialized at first, since the ctors of other
// members may rely on it (e.g., FileDescriptorWatcher).
base::test::TaskEnvironment task_environment_{
base::test::TaskEnvironment::MainThreadType::IO};
FakeProcessRunner process_runner_;
NoopSystem system_;
NiceMock<MockDatapath> datapath_;
FakeDNSClientFactory* dns_factory_; // Owned by |qos_svc_|.
FakeShillClientForQoS shill_client_;
NiceMock<MockConntrackMonitor> conntrack_monitor_;
QoSService qos_svc_;
};
// Verifies the interactions between QoSService and Datapath when feature on the
// events of feature enable/disable and device change events.
TEST_F(QoSServiceTest, EnableDisableQoSFeature) {
const ShillClient::Device kEth0 = {
.technology = net_base::Technology::kEthernet,
.ifname = "eth0",
};
const ShillClient::Device kEth1 = {
.technology = net_base::Technology::kEthernet,
.ifname = "eth1",
};
const ShillClient::Device kWlan0 = {
.technology = net_base::Technology::kWiFi,
.ifname = "wlan0",
};
const ShillClient::Device kWlan1 = {
.technology = net_base::Technology::kWiFi,
.ifname = "wlan1",
};
// No interaction with Datapath before feature is enabled.
EXPECT_CALL(datapath_, EnableQoSDetection).Times(0);
EXPECT_CALL(datapath_, EnableQoSApplyingDSCP).Times(0);
qos_svc_.OnPhysicalDeviceAdded(kEth0);
qos_svc_.OnPhysicalDeviceAdded(kWlan0);
qos_svc_.OnPhysicalDeviceRemoved(kWlan0);
qos_svc_.OnPhysicalDeviceAdded(kWlan0);
Mock::VerifyAndClearExpectations(&datapath_);
// On feature enabled, the detection chain should be enabled, and the DSCP
// marking chain for the existing interface should be enabled.
EXPECT_CALL(datapath_, EnableQoSDetection);
EXPECT_CALL(datapath_, EnableQoSApplyingDSCP("wlan0"));
qos_svc_.Enable();
Mock::VerifyAndClearExpectations(&datapath_);
// No interaction with Datapath on uninteresting or already-tracked
// interfaces.
EXPECT_CALL(datapath_, EnableQoSDetection).Times(0);
EXPECT_CALL(datapath_, EnableQoSApplyingDSCP).Times(0);
qos_svc_.OnPhysicalDeviceAdded(kEth1);
qos_svc_.OnPhysicalDeviceAdded(kWlan0);
Mock::VerifyAndClearExpectations(&datapath_);
// Device change events on interesting interfaces.
EXPECT_CALL(datapath_, DisableQoSApplyingDSCP("wlan0"));
EXPECT_CALL(datapath_, EnableQoSApplyingDSCP("wlan1"));
qos_svc_.OnPhysicalDeviceRemoved(kWlan0);
qos_svc_.OnPhysicalDeviceAdded(kWlan1);
Mock::VerifyAndClearExpectations(&datapath_);
// On feature disabled.
EXPECT_CALL(datapath_, DisableQoSDetection);
EXPECT_CALL(datapath_, DisableQoSApplyingDSCP("wlan1"));
qos_svc_.Disable();
Mock::VerifyAndClearExpectations(&datapath_);
// Device change events when disabled, and then enable again.
qos_svc_.OnPhysicalDeviceRemoved(kWlan1);
qos_svc_.OnPhysicalDeviceAdded(kWlan0);
EXPECT_CALL(datapath_, EnableQoSDetection);
EXPECT_CALL(datapath_, EnableQoSApplyingDSCP("wlan0"));
qos_svc_.Enable();
Mock::VerifyAndClearExpectations(&datapath_);
}
// Verifies that ProcessSocketConnectionEvent behaves correctly when
// feature on the events of feature enable/disable.
TEST_F(QoSServiceTest, ProcessSocketConnectionEvent) {
auto updater = std::make_unique<MockConnmarkUpdater>(&conntrack_monitor_);
auto updater_ptr = updater.get();
qos_svc_.SetConnmarkUpdaterForTesting(std::move(updater));
std::unique_ptr<patchpanel::SocketConnectionEvent> open_msg =
CreateOpenSocketConnectionEvent();
std::unique_ptr<patchpanel::SocketConnectionEvent> close_msg =
CreateCloseSocketConnectionEvent();
// No interaction with ConnmarkUpdater before feature is enabled.
EXPECT_CALL(*updater_ptr, UpdateConnmark).Times(0);
qos_svc_.ProcessSocketConnectionEvent(*open_msg);
Mock::VerifyAndClearExpectations(updater_ptr);
// After feature is enabled, process socket connection event will trigger
// corresponding connmark update.
qos_svc_.Enable();
// When enabling QoS service, a new ConnmarkUpdater will be assigned, so
// assign mock ConnmarkUpdater to QoS service again.
updater = std::make_unique<MockConnmarkUpdater>(&conntrack_monitor_);
updater_ptr = updater.get();
qos_svc_.SetConnmarkUpdaterForTesting(std::move(updater));
auto tcp_conn = ConnmarkUpdater::Conntrack5Tuple{
.src_addr = *(net_base::IPAddress::CreateFromString(kIPAddress1)),
.dst_addr = *(net_base::IPAddress::CreateFromString(kIPAddress2)),
.sport = static_cast<uint16_t>(kPort1),
.dport = static_cast<uint16_t>(kPort2),
.proto = ConnmarkUpdater::IPProtocol::kTCP};
EXPECT_CALL(
*updater_ptr,
UpdateConnmark(Eq(tcp_conn),
Fwmark::FromQoSCategory(QoSCategory::kRealTimeInteractive),
kFwmarkQoSCategoryMask));
qos_svc_.ProcessSocketConnectionEvent(*open_msg);
EXPECT_CALL(*updater_ptr,
UpdateConnmark(Eq(tcp_conn),
Fwmark::FromQoSCategory(QoSCategory::kDefault),
kFwmarkQoSCategoryMask));
qos_svc_.ProcessSocketConnectionEvent(*close_msg);
Mock::VerifyAndClearExpectations(updater_ptr);
// No interaction with ConnmarkUpdater after feature is disabled.
EXPECT_CALL(*updater_ptr, UpdateConnmark).Times(0);
qos_svc_.Disable();
qos_svc_.ProcessSocketConnectionEvent(*open_msg);
Mock::VerifyAndClearExpectations(updater_ptr);
}
// QoSService should start DNS queries for each valid hostname in DoHProviders,
// and Datapath will be notified when all DNS queries finished.
TEST_F(QoSServiceTest, UpdateDoHProviders) {
const auto wlan0_device =
CreateShillDevice("wlan0", /*is_connected=*/true, {kIPv4DNS});
shill_client_.set_devices({wlan0_device});
qos_svc_.OnPhysicalDeviceAdded(wlan0_device);
// Update DoH list with 2 valid entries. There should be 4 DNS requests in
// total.
const ShillClient::DoHProviders doh_list = {
"https://url-a",
"https://url-b",
"http://want-https",
"no-https-prefix",
"", // check that no crash for empty string
};
EXPECT_CALL(*dns_factory_,
Resolve(net_base::IPFamily::kIPv4, "url-a", _, _, _));
EXPECT_CALL(*dns_factory_,
Resolve(net_base::IPFamily::kIPv6, "url-a", _, _, _));
EXPECT_CALL(*dns_factory_,
Resolve(net_base::IPFamily::kIPv4, "url-b", _, _, _));
EXPECT_CALL(*dns_factory_,
Resolve(net_base::IPFamily::kIPv6, "url-b", _, _, _));
shill_client_.set_doh_providers(doh_list);
qos_svc_.OnDoHProvidersChanged();
ASSERT_EQ(2, dns_factory_->ipv4_callbacks().size());
ASSERT_EQ(2, dns_factory_->ipv6_callbacks().size());
// Datapath methods should only be invoked when we get all the callbacks.
const auto kIPv4Addr1 = IPAddress::CreateFromString("1.2.3.4").value();
const auto kIPv4Addr2 = IPAddress::CreateFromString("1.2.3.5").value();
const auto kIPv6Addr1 = IPAddress::CreateFromString("fd00::1").value();
const auto kIPv6Addr2 = IPAddress::CreateFromString("fd00::2").value();
EXPECT_CALL(datapath_, UpdateDoHProvidersForQoS).Times(0);
dns_factory_->TriggerIPv4Callback(DNSClient::Result({kIPv4Addr1}));
dns_factory_->TriggerIPv4Callback(
DNSClient::Result({kIPv4Addr1, kIPv4Addr2}));
dns_factory_->TriggerIPv6Callback(
DNSClient::Result({kIPv6Addr1, kIPv6Addr2}));
EXPECT_CALL(datapath_, UpdateDoHProvidersForQoS(
IpFamily::kIPv4, std::vector<net_base::IPAddress>{
kIPv4Addr1, kIPv4Addr2}));
EXPECT_CALL(datapath_, UpdateDoHProvidersForQoS(
IpFamily::kIPv6, std::vector<net_base::IPAddress>{
kIPv6Addr1, kIPv6Addr2}));
// Trigger the last callback with an error.
dns_factory_->TriggerIPv6Callback(
DNSClient::Result(base::unexpected(DNSClient::Error::kRefused)));
}
// Datapath should be notified when DoH provider list is empty.
TEST_F(QoSServiceTest, UpdateDoHProvidersEmptyInput) {
const auto wlan0_device =
CreateShillDevice("wlan0", /*is_connected=*/true, {kIPv4DNS});
shill_client_.set_devices({wlan0_device});
qos_svc_.OnPhysicalDeviceAdded(wlan0_device);
EXPECT_CALL(datapath_,
UpdateDoHProvidersForQoS(IpFamily::kIPv4,
std::vector<net_base::IPAddress>{}));
EXPECT_CALL(datapath_,
UpdateDoHProvidersForQoS(IpFamily::kIPv6,
std::vector<net_base::IPAddress>{}));
shill_client_.set_doh_providers({});
qos_svc_.OnDoHProvidersChanged();
}
// Datapath should be notified when the resolved result is empty.
TEST_F(QoSServiceTest, UpdateDoHProvidersEmptyResolveResult) {
const auto wlan0_device =
CreateShillDevice("wlan0", /*is_connected=*/true, {kIPv4DNS});
shill_client_.set_devices({wlan0_device});
qos_svc_.OnPhysicalDeviceAdded(wlan0_device);
shill_client_.set_doh_providers({"https://url-a", "https://url-b"});
qos_svc_.OnDoHProvidersChanged();
EXPECT_CALL(datapath_,
UpdateDoHProvidersForQoS(IpFamily::kIPv4,
std::vector<net_base::IPAddress>{}));
EXPECT_CALL(datapath_,
UpdateDoHProvidersForQoS(IpFamily::kIPv6,
std::vector<net_base::IPAddress>{}));
dns_factory_->TriggerIPv4Callback(
DNSClient::Result(base::unexpected(DNSClient::Error::kNoData)));
dns_factory_->TriggerIPv4Callback(
DNSClient::Result(base::unexpected(DNSClient::Error::kRefused)));
dns_factory_->TriggerIPv6Callback(
DNSClient::Result(base::unexpected(DNSClient::Error::kBadQuery)));
dns_factory_->TriggerIPv6Callback(
DNSClient::Result(base::unexpected(DNSClient::Error::kBadResp)));
}
// If the DoH provider list is updated again when we are still processing the
// previous update, all the ongoing DNS requests should be cancelled.
TEST_F(QoSServiceTest, UpdateDoHProvidersInvalidateOngoingQueries) {
const auto wlan0_device =
CreateShillDevice("wlan0", /*is_connected=*/true, {kIPv4DNS});
shill_client_.set_devices({wlan0_device});
qos_svc_.OnPhysicalDeviceAdded(wlan0_device);
shill_client_.set_doh_providers({"https://url-a", "https://url-b"});
qos_svc_.OnDoHProvidersChanged();
auto client_ptrs = dns_factory_->GetWeakPtrsToExistingClients();
ASSERT_EQ(client_ptrs.size(), 4);
// Nothing will happen if the DoH providers are not changed.
qos_svc_.OnDoHProvidersChanged();
for (const auto ptr : client_ptrs) {
EXPECT_FALSE(ptr.WasInvalidated());
}
// Ongoing tasks should be cancelled if DoH providers are changed.
shill_client_.set_doh_providers({"https://url-d", "https://url-e"});
qos_svc_.OnDoHProvidersChanged();
for (const auto ptr : client_ptrs) {
EXPECT_TRUE(ptr.WasInvalidated());
}
}
// Verify that DNS clients are started properly on IPConfig change events. The
// interaction with Datapath is verified in the above tests and thus skipped in
// this test.
TEST_F(QoSServiceTest, UpdateDoHProvidersIPConfigChanged) {
shill_client_.set_doh_providers({"https://url-a"});
qos_svc_.OnDoHProvidersChanged();
const auto wlan0_connected_dns_v4 =
CreateShillDevice("wlan0", /*is_connected=*/true, {kIPv4DNS});
const auto wlan0_not_connected_dns_v4 =
CreateShillDevice("wlan0", /*is_connected=*/false, {kIPv4DNS});
const auto wlan0_connected_dns_dual =
CreateShillDevice("wlan0", /*is_connected=*/true, {kIPv4DNS, kIPv6DNS});
// Connected but the device is not tracked.
EXPECT_CALL(*dns_factory_, Resolve).Times(0);
qos_svc_.OnIPConfigChanged(wlan0_connected_dns_v4);
Mock::VerifyAndClearExpectations(dns_factory_);
// Device is tracked but not connected.
EXPECT_CALL(*dns_factory_, Resolve).Times(0);
qos_svc_.OnPhysicalDeviceAdded(wlan0_not_connected_dns_v4);
qos_svc_.OnIPConfigChanged(wlan0_not_connected_dns_v4);
Mock::VerifyAndClearExpectations(dns_factory_);
// Device is tracked and connected, DNS clients should be created (1 DoH
// provider x 2 IP family).
EXPECT_CALL(*dns_factory_, Resolve).Times(2);
qos_svc_.OnIPConfigChanged(wlan0_connected_dns_v4);
Mock::VerifyAndClearExpectations(dns_factory_);
// DNS servers changed, DNS clients should be created again (1 DoH provider x
// 2 IP family x 2 dns servers)
EXPECT_CALL(*dns_factory_, Resolve).Times(4);
qos_svc_.OnIPConfigChanged(wlan0_connected_dns_dual);
Mock::VerifyAndClearExpectations(dns_factory_);
// DNS servers not changed, DNS clients should no be created.
EXPECT_CALL(*dns_factory_, Resolve).Times(0);
qos_svc_.OnIPConfigChanged(wlan0_connected_dns_dual);
Mock::VerifyAndClearExpectations(dns_factory_);
}
TEST_F(QoSServiceTest, OnBorealisVMStarted) {
const auto borealis_ipv4_subnet =
net_base::IPv4CIDR::CreateFromCIDRString("100.115.93.0/29").value();
auto ipv4_subnet =
std::make_unique<Subnet>(borealis_ipv4_subnet, base::DoNothing());
CrostiniService::CrostiniDevice borealis_device(
CrostiniService::VMType::kBorealis, "vmtap1", std::move(ipv4_subnet),
nullptr);
EXPECT_CALL(datapath_, AddBorealisQoSRule("vmtap1"));
qos_svc_.OnBorealisVMStarted("vmtap1");
}
TEST_F(QoSServiceTest, OnBorealisVMStopped) {
const auto borealis_ipv4_subnet =
*net_base::IPv4CIDR::CreateFromCIDRString("100.115.93.0/29");
auto ipv4_subnet =
std::make_unique<Subnet>(borealis_ipv4_subnet, base::DoNothing());
CrostiniService::CrostiniDevice borealis_device(
CrostiniService::VMType::kBorealis, "vmtap1", std::move(ipv4_subnet),
nullptr);
EXPECT_CALL(datapath_, RemoveBorealisQoSRule("vmtap1"));
qos_svc_.OnBorealisVMStopped("vmtap1");
}
// QoSService can handle socket connection events correctly. When socket
// connection event is received, call ConnmarkUpdater to handle the update
// task.
TEST_F(QoSServiceTest, HandleSocketConnectionEvent) {
std::unique_ptr<patchpanel::SocketConnectionEvent> open_msg =
CreateOpenSocketConnectionEvent();
qos_svc_.Enable();
auto updater = std::make_unique<MockConnmarkUpdater>(&conntrack_monitor_);
auto updater_ptr = updater.get();
qos_svc_.SetConnmarkUpdaterForTesting(std::move(updater));
// When notified of TCP socket event, call ConnmarkUpdater for connmark
// update for this connection.
auto tcp_conn = ConnmarkUpdater::Conntrack5Tuple{
.src_addr = *(net_base::IPAddress::CreateFromString(kIPAddress1)),
.dst_addr = *(net_base::IPAddress::CreateFromString(kIPAddress2)),
.sport = static_cast<uint16_t>(kPort1),
.dport = static_cast<uint16_t>(kPort2),
.proto = ConnmarkUpdater::IPProtocol::kTCP};
EXPECT_CALL(
*updater_ptr,
UpdateConnmark(Eq(tcp_conn),
Fwmark::FromQoSCategory(QoSCategory::kRealTimeInteractive),
kFwmarkQoSCategoryMask));
qos_svc_.ProcessSocketConnectionEvent(*open_msg);
Mock::VerifyAndClearExpectations(updater_ptr);
// When notified of UDP socket event, call ConnmarkUpdater for connmark
// update for this connection.
auto udp_conn = ConnmarkUpdater::Conntrack5Tuple{
.src_addr = *(net_base::IPAddress::CreateFromString(kIPAddress1)),
.dst_addr = *(net_base::IPAddress::CreateFromString(kIPAddress2)),
.sport = static_cast<uint16_t>(kPort1),
.dport = static_cast<uint16_t>(kPort2),
.proto = ConnmarkUpdater::IPProtocol::kUDP};
EXPECT_CALL(
*updater_ptr,
UpdateConnmark(Eq(udp_conn),
Fwmark::FromQoSCategory(QoSCategory::kRealTimeInteractive),
kFwmarkQoSCategoryMask));
open_msg->set_proto(patchpanel::SocketConnectionEvent::IpProtocol::
SocketConnectionEvent_IpProtocol_UDP);
qos_svc_.ProcessSocketConnectionEvent(*open_msg);
Mock::VerifyAndClearExpectations(updater_ptr);
}
} // namespace
} // namespace patchpanel