| // Copyright 2021 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "dns-proxy/proxy.h" |
| |
| #include <fcntl.h> |
| #include <linux/rtnetlink.h> |
| #include <sys/stat.h> |
| |
| #include <memory> |
| #include <optional> |
| #include <utility> |
| #include <vector> |
| |
| #include <base/functional/callback.h> |
| #include <chromeos/patchpanel/dbus/fake_client.h> |
| #include <chromeos/patchpanel/mock_message_dispatcher.h> |
| #include <dbus/mock_bus.h> |
| #include <dbus/mock_object_proxy.h> |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| #include <net-base/byte_utils.h> |
| #include <net-base/rtnl_message.h> |
| #include <shill/dbus/client/fake_client.h> |
| #include <shill/dbus-constants.h> |
| #include <shill/dbus-proxy-mocks.h> |
| |
| #include "dns-proxy/ipc.pb.h" |
| |
| namespace dns_proxy { |
| namespace { |
| |
| constexpr net_base::IPv4Address kNetnsPeerIPv4Addr(100, 115, 92, 130); |
| constexpr net_base::IPv6Address kNetnsPeerIPv6Addr( |
| 0xfd, 0x05, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01); |
| constexpr base::TimeDelta kRequestTimeout = base::Seconds(10000); |
| constexpr base::TimeDelta kRequestRetryDelay = base::Milliseconds(200); |
| constexpr int32_t kRequestMaxRetry = 1; |
| |
| int make_fd() { |
| std::string fn( |
| ::testing::UnitTest::GetInstance()->current_test_info()->name()); |
| fn = "/tmp/" + fn; |
| return open(fn.c_str(), O_CREAT, 0600); |
| } |
| } // namespace |
| using org::chromium::flimflam::ManagerProxyInterface; |
| using org::chromium::flimflam::ManagerProxyMock; |
| using testing::_; |
| using testing::ByMove; |
| using testing::DoAll; |
| using testing::ElementsAre; |
| using testing::IsEmpty; |
| using testing::Return; |
| using testing::SetArgPointee; |
| using testing::StrEq; |
| |
| MATCHER_P(EqualsProto, |
| message, |
| "Match a proto Message equal to the matcher's argument.") { |
| std::string expected_serialized, actual_serialized; |
| message.SerializeToString(&expected_serialized); |
| arg.SerializeToString(&actual_serialized); |
| return expected_serialized == actual_serialized; |
| } |
| |
| patchpanel::Client::VirtualDevice virtualdev( |
| patchpanel::Client::GuestType guest_type, |
| const std::string& ifname, |
| const std::string& phys_ifname) { |
| patchpanel::Client::VirtualDevice device; |
| device.ifname = ifname; |
| device.phys_ifname = phys_ifname; |
| device.guest_type = guest_type; |
| return device; |
| } |
| |
| class FakeShillClient : public shill::FakeClient { |
| public: |
| FakeShillClient(scoped_refptr<dbus::Bus> bus, |
| ManagerProxyInterface* manager_proxy) |
| : shill::FakeClient(bus), manager_proxy_(manager_proxy) {} |
| |
| std::unique_ptr<shill::Client::ManagerPropertyAccessor> ManagerProperties( |
| const base::TimeDelta& timeout) const override { |
| return std::make_unique<shill::Client::ManagerPropertyAccessor>( |
| manager_proxy_); |
| } |
| |
| std::unique_ptr<shill::Client::Device> DefaultDevice( |
| bool exclude_vpn) override { |
| return std::move(default_device_); |
| } |
| |
| ManagerProxyInterface* GetManagerProxy() const override { |
| return manager_proxy_; |
| } |
| |
| std::unique_ptr<shill::Client::Device> default_device_; |
| |
| private: |
| ManagerProxyInterface* manager_proxy_; |
| }; |
| |
| class MockPatchpanelClient : public patchpanel::FakeClient { |
| public: |
| MockPatchpanelClient() = default; |
| ~MockPatchpanelClient() override = default; |
| |
| MOCK_METHOD( |
| (std::pair<base::ScopedFD, patchpanel::Client::ConnectedNamespace>), |
| ConnectNamespace, |
| (pid_t pid, |
| const std::string& outbound_ifname, |
| bool forward_user_traffic, |
| bool route_on_vpn, |
| patchpanel::Client::TrafficSource traffic_source, |
| bool static_ipv6), |
| (override)); |
| MOCK_METHOD(base::ScopedFD, |
| RedirectDns, |
| (patchpanel::Client::DnsRedirectionRequestType, |
| const std::string&, |
| const std::string&, |
| const std::vector<std::string>&, |
| const std::string&), |
| (override)); |
| MOCK_METHOD(std::vector<patchpanel::Client::VirtualDevice>, |
| GetDevices, |
| (), |
| (override)); |
| }; |
| |
| class MockResolver : public Resolver { |
| public: |
| MockResolver() |
| : Resolver(base::DoNothing(), |
| kRequestTimeout, |
| kRequestRetryDelay, |
| kRequestMaxRetry) {} |
| ~MockResolver() override = default; |
| |
| MOCK_METHOD(bool, ListenUDP, (struct sockaddr*), (override)); |
| MOCK_METHOD(bool, ListenTCP, (struct sockaddr*), (override)); |
| MOCK_METHOD(void, |
| SetNameServers, |
| (const std::vector<std::string>&), |
| (override)); |
| MOCK_METHOD(void, |
| SetDoHProviders, |
| (const std::vector<std::string>&, bool), |
| (override)); |
| }; |
| |
| class TestProxy : public Proxy { |
| public: |
| TestProxy(const Options& opts, |
| std::unique_ptr<patchpanel::Client> patchpanel, |
| std::unique_ptr<shill::Client> shill, |
| std::unique_ptr<patchpanel::MessageDispatcher<ProxyAddrMessage>> |
| msg_dispatcher) |
| : Proxy(opts, |
| std::move(patchpanel), |
| std::move(shill), |
| std::move(msg_dispatcher)) {} |
| |
| std::unique_ptr<Resolver> resolver; |
| std::unique_ptr<Resolver> NewResolver(base::TimeDelta timeout, |
| base::TimeDelta retry_delay, |
| int max_num_retries) override { |
| return std::move(resolver); |
| } |
| }; |
| |
| class ProxyTest : public ::testing::Test { |
| protected: |
| ProxyTest() |
| : mock_bus_(new dbus::MockBus{dbus::Bus::Options{}}), |
| mock_proxy_(new dbus::MockObjectProxy(mock_bus_.get(), |
| shill::kFlimflamServiceName, |
| dbus::ObjectPath("/"))) {} |
| ~ProxyTest() override { mock_bus_->ShutdownAndBlock(); } |
| |
| void SetUp() override { |
| EXPECT_CALL(*mock_bus_, GetObjectProxy(_, _)) |
| .WillRepeatedly(Return(mock_proxy_.get())); |
| } |
| |
| void SetUpProxy(const Proxy::Options& opts, |
| std::unique_ptr<shill::Client::Device> device = nullptr, |
| bool set_resolver = true) { |
| // Set up mocks and fakes. |
| patchpanel_client_ = new MockPatchpanelClient(); |
| shill_client_ = new FakeShillClient( |
| mock_bus_, reinterpret_cast<ManagerProxyInterface*>( |
| const_cast<ManagerProxyMock*>(&mock_manager_))); |
| msg_dispatcher_ = new patchpanel::MockMessageDispatcher<ProxyAddrMessage>(); |
| |
| // Initialize Proxy instance. |
| proxy_ = std::make_unique<TestProxy>( |
| opts, base::WrapUnique(patchpanel_client_), |
| base::WrapUnique(shill_client_), base::WrapUnique(msg_dispatcher_)); |
| |
| // Initialize default proxy behaviors. |
| proxy_->shill_ready_ = true; |
| proxy_->device_ = std::move(device); |
| if (set_resolver) { |
| resolver_ = new MockResolver(); |
| proxy_->resolver_ = base::WrapUnique(resolver_); |
| proxy_->doh_config_.set_resolver(resolver_); |
| } |
| |
| // Initialize default mocks behavior. |
| if (opts.type == Proxy::Type::kSystem) { |
| ON_CALL(mock_manager_, SetDNSProxyAddresses(_, _, _)) |
| .WillByDefault(Return(true)); |
| ON_CALL(*msg_dispatcher_, SendMessage(_)).WillByDefault(Return(true)); |
| } |
| } |
| |
| std::unique_ptr<shill::Client::Device> ShillDevice( |
| shill::Client::Device::ConnectionState state = |
| shill::Client::Device::ConnectionState::kOnline, |
| shill::Client::Device::Type type = shill::Client::Device::Type::kEthernet, |
| const std::string& ifname = "", |
| std::vector<std::string> ipv4_nameservers = {"8.8.8.8"}, |
| std::vector<std::string> ipv6_nameservers = {"2001:4860:4860::8888"}) { |
| auto dev = std::make_unique<shill::Client::Device>(); |
| dev->type = type; |
| dev->state = state; |
| dev->ifname = ifname; |
| dev->ipconfig.ipv4_dns_addresses = ipv4_nameservers; |
| dev->ipconfig.ipv6_dns_addresses = ipv6_nameservers; |
| return dev; |
| } |
| |
| void SetNamespaceAddresses( |
| const std::optional<net_base::IPv4Address>& ipv4_addr, |
| const std::optional<net_base::IPv6Address>& ipv6_addr) { |
| proxy_->ns_fd_ = base::ScopedFD(make_fd()); |
| if (ipv4_addr) { |
| proxy_->ns_.peer_ipv4_address = *ipv4_addr; |
| } |
| if (ipv6_addr) { |
| proxy_->ns_peer_ipv6_address_ = *ipv6_addr; |
| } |
| } |
| |
| void SetNameServers(const std::vector<std::string>& ipv4_nameservers, |
| const std::vector<std::string>& ipv6_nameservers) { |
| EXPECT_TRUE(proxy_->device_); |
| proxy_->device_->ipconfig.ipv4_dns_addresses = ipv4_nameservers; |
| proxy_->device_->ipconfig.ipv6_dns_addresses = ipv6_nameservers; |
| proxy_->UpdateNameServers(); |
| } |
| |
| protected: |
| scoped_refptr<dbus::MockBus> mock_bus_; |
| scoped_refptr<dbus::MockObjectProxy> mock_proxy_; |
| ManagerProxyMock mock_manager_; |
| |
| MockResolver* resolver_; |
| patchpanel::MockMessageDispatcher<ProxyAddrMessage>* msg_dispatcher_; |
| FakeShillClient* shill_client_; |
| MockPatchpanelClient* patchpanel_client_; |
| std::unique_ptr<TestProxy> proxy_; |
| }; |
| |
| TEST_F(ProxyTest, SystemProxy_OnShutdownClearsAddressPropertyOnShill) { |
| EXPECT_CALL(mock_manager_, ClearDNSProxyAddresses(_, _)); |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kSystem}); |
| int unused; |
| proxy_->OnShutdown(&unused); |
| } |
| |
| TEST_F(ProxyTest, NonSystemProxy_OnShutdownDoesNotCallShill) { |
| EXPECT_CALL(mock_manager_, SetDNSProxyAddresses(_, _, _)).Times(0); |
| EXPECT_CALL(mock_manager_, ClearDNSProxyAddresses(_, _)).Times(0); |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kDefault}, ShillDevice()); |
| int unused; |
| proxy_->OnShutdown(&unused); |
| } |
| |
| TEST_F(ProxyTest, SystemProxy_SetShillDNSProxyAddressesDoesntCrashIfDieFalse) { |
| ::testing::FLAGS_gtest_death_test_style = "threadsafe"; |
| EXPECT_CALL(mock_manager_, SetProperty(_, _, _, _)).Times(0); |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kSystem}); |
| proxy_->SetShillDNSProxyAddresses(kNetnsPeerIPv4Addr, kNetnsPeerIPv6Addr, |
| /*die_on_failure=*/false, |
| /*num_retries=*/0); |
| } |
| |
| TEST_F(ProxyTest, SystemProxy_SetShillDNSProxyAddresses) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kSystem}, ShillDevice()); |
| SetNameServers({"8.8.8.8"}, {"2001:4860:4860::8888"}); |
| EXPECT_CALL(mock_manager_, |
| SetDNSProxyAddresses(ElementsAre(kNetnsPeerIPv4Addr.ToString(), |
| kNetnsPeerIPv6Addr.ToString()), |
| _, _)) |
| .WillOnce(Return(true)); |
| proxy_->SetShillDNSProxyAddresses(kNetnsPeerIPv4Addr, kNetnsPeerIPv6Addr); |
| } |
| |
| TEST_F(ProxyTest, SystemProxy_SetShillDNSProxyAddressesEmptyNameserver) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kSystem}, ShillDevice()); |
| |
| // Only IPv4 nameserver. |
| SetNameServers({"8.8.8.8"}, /*ipv6_nameservers=*/{}); |
| EXPECT_CALL( |
| mock_manager_, |
| SetDNSProxyAddresses(ElementsAre(kNetnsPeerIPv4Addr.ToString()), _, _)) |
| .WillOnce(Return(true)); |
| proxy_->SetShillDNSProxyAddresses(kNetnsPeerIPv4Addr, kNetnsPeerIPv6Addr); |
| |
| // Only IPv6 nameserver. |
| SetNameServers(/*ipv4_nameservers=*/{}, {"2001:4860:4860::8888"}); |
| EXPECT_CALL( |
| mock_manager_, |
| SetDNSProxyAddresses(ElementsAre(kNetnsPeerIPv6Addr.ToString()), _, _)) |
| .WillOnce(Return(true)); |
| proxy_->SetShillDNSProxyAddresses(kNetnsPeerIPv4Addr, kNetnsPeerIPv6Addr); |
| } |
| |
| TEST_F(ProxyTest, SystemProxy_ClearShillDNSProxyAddresses) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kSystem}); |
| EXPECT_CALL(mock_manager_, ClearDNSProxyAddresses(_, _)); |
| proxy_->ClearShillDNSProxyAddresses(); |
| } |
| |
| TEST_F(ProxyTest, SystemProxy_SendIPAddressesToController) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kSystem}, ShillDevice()); |
| SetNameServers({"8.8.8.8"}, {"2001:4860:4860::8888"}); |
| |
| ProxyAddrMessage msg; |
| msg.set_type(ProxyAddrMessage::SET_ADDRS); |
| msg.add_addrs(kNetnsPeerIPv4Addr.ToString()); |
| msg.add_addrs(kNetnsPeerIPv6Addr.ToString()); |
| EXPECT_CALL(*msg_dispatcher_, SendMessage(EqualsProto(msg))) |
| .WillOnce(Return(true)); |
| proxy_->SendIPAddressesToController(kNetnsPeerIPv4Addr, kNetnsPeerIPv6Addr); |
| } |
| |
| TEST_F(ProxyTest, SystemProxy_SendIPAddressesToControllerEmptyNameserver) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kSystem}, ShillDevice()); |
| |
| // Only IPv4 nameserver. |
| SetNameServers({"8.8.8.8"}, /*ipv6_nameservers=*/{}); |
| ProxyAddrMessage msg; |
| msg.set_type(ProxyAddrMessage::SET_ADDRS); |
| msg.add_addrs(kNetnsPeerIPv4Addr.ToString()); |
| EXPECT_CALL(*msg_dispatcher_, SendMessage(EqualsProto(msg))) |
| .WillOnce(Return(true)); |
| proxy_->SendIPAddressesToController(kNetnsPeerIPv4Addr, kNetnsPeerIPv6Addr); |
| |
| // Only IPv6 nameserver. |
| SetNameServers(/*ipv4_nameservers=*/{}, {"2001:4860:4860::8888"}); |
| msg.Clear(); |
| msg.set_type(ProxyAddrMessage::SET_ADDRS); |
| msg.add_addrs(kNetnsPeerIPv6Addr.ToString()); |
| EXPECT_CALL(*msg_dispatcher_, SendMessage(EqualsProto(msg))) |
| .WillOnce(Return(true)); |
| proxy_->SendIPAddressesToController(kNetnsPeerIPv4Addr, kNetnsPeerIPv6Addr); |
| } |
| |
| TEST_F(ProxyTest, SystemProxy_ClearIPAddressesInController) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kSystem}); |
| EXPECT_CALL(*msg_dispatcher_, SendMessage(_)).WillOnce(Return(true)); |
| proxy_->ClearIPAddressesInController(); |
| } |
| |
| TEST_F(ProxyTest, ShillInitializedWhenReady) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kSystem}); |
| |
| // Test class defaults to make shill client ready. Reset to false. |
| proxy_->shill_ready_ = false; |
| proxy_->OnShillReady(true); |
| EXPECT_TRUE(proxy_->shill_ready_); |
| } |
| |
| TEST_F(ProxyTest, SystemProxy_ConnectedNamedspace) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kSystem}); |
| |
| EXPECT_CALL( |
| *patchpanel_client_, |
| ConnectNamespace(_, _, /*outbound_ifname=*/"", /*route_on_vpn=*/false, |
| patchpanel::Client::TrafficSource::kSystem, _)) |
| .WillOnce( |
| Return(std::make_pair(base::ScopedFD(make_fd()), |
| patchpanel::Client::ConnectedNamespace{}))); |
| proxy_->OnPatchpanelReady(true); |
| } |
| |
| TEST_F(ProxyTest, DefaultProxy_ConnectedNamedspace) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kDefault}, ShillDevice()); |
| |
| EXPECT_CALL( |
| *patchpanel_client_, |
| ConnectNamespace(_, _, /*outbound_ifname=*/"", /*route_on_vpn=*/true, |
| patchpanel::Client::TrafficSource::kUser, _)) |
| .WillOnce( |
| Return(std::make_pair(base::ScopedFD(make_fd()), |
| patchpanel::Client::ConnectedNamespace{}))); |
| proxy_->OnPatchpanelReady(true); |
| } |
| |
| TEST_F(ProxyTest, ArcProxy_ConnectedNamedspace) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kARC, .ifname = "eth0"}); |
| |
| EXPECT_CALL(*patchpanel_client_, |
| ConnectNamespace(_, _, /*outbound_ifname=*/"eth0", |
| /*route_on_vpn=*/false, |
| patchpanel::Client::TrafficSource::kArc, _)) |
| .WillOnce( |
| Return(std::make_pair(base::ScopedFD(make_fd()), |
| patchpanel::Client::ConnectedNamespace{}))); |
| proxy_->OnPatchpanelReady(true); |
| } |
| |
| TEST_F(ProxyTest, ShillResetRestoresAddressProperty) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kSystem}, ShillDevice()); |
| SetNameServers({"8.8.8.8"}, {"2001:4860:4860::8888"}); |
| SetNamespaceAddresses(kNetnsPeerIPv4Addr, kNetnsPeerIPv6Addr); |
| |
| EXPECT_CALL(mock_manager_, |
| SetDNSProxyAddresses(ElementsAre(kNetnsPeerIPv4Addr.ToString(), |
| kNetnsPeerIPv6Addr.ToString()), |
| _, _)) |
| .WillOnce(Return(true)); |
| proxy_->OnShillReset(true); |
| } |
| |
| TEST_F(ProxyTest, StateClearedIfDefaultServiceDrops) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kSystem}, ShillDevice()); |
| |
| proxy_->OnDefaultDeviceChanged(nullptr /* no service */); |
| EXPECT_FALSE(proxy_->device_); |
| EXPECT_FALSE(proxy_->resolver_); |
| } |
| |
| TEST_F(ProxyTest, ArcProxy_IgnoredIfDefaultServiceDrops) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kARC, .ifname = "eth0"}, |
| ShillDevice()); |
| |
| proxy_->OnDefaultDeviceChanged(nullptr /* no service */); |
| EXPECT_TRUE(proxy_->device_); |
| EXPECT_TRUE(proxy_->resolver_); |
| } |
| |
| TEST_F(ProxyTest, StateClearedIfDefaultServiceIsNotOnline) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kSystem}, ShillDevice()); |
| |
| auto dev = ShillDevice(shill::Client::Device::ConnectionState::kReady); |
| proxy_->OnDefaultDeviceChanged(dev.get()); |
| |
| EXPECT_FALSE(proxy_->device_); |
| EXPECT_FALSE(proxy_->resolver_); |
| } |
| |
| TEST_F(ProxyTest, NewResolverStartsListeningOnDefaultServiceComesOnline) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kDefault}, |
| /*device=*/nullptr, /*set_resolver=*/false); |
| |
| auto* new_resolver = new MockResolver(); |
| proxy_->resolver = base::WrapUnique(new_resolver); |
| EXPECT_CALL(*new_resolver, ListenUDP(_)).WillOnce(Return(true)); |
| EXPECT_CALL(*new_resolver, ListenTCP(_)).WillOnce(Return(true)); |
| |
| auto dev = ShillDevice(shill::Client::Device::ConnectionState::kOnline); |
| brillo::VariantDictionary props; |
| EXPECT_CALL(mock_manager_, GetProperties(_, _, _)) |
| .WillOnce(DoAll(SetArgPointee<0>(props), Return(true))); |
| proxy_->OnDefaultDeviceChanged(dev.get()); |
| EXPECT_TRUE(proxy_->resolver_); |
| } |
| |
| TEST_F(ProxyTest, NameServersUpdatedOnDefaultServiceComesOnline) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kDefault}); |
| |
| auto dev = ShillDevice(shill::Client::Device::ConnectionState::kOnline, |
| shill::Client::Device::Type::kEthernet, "eth0", |
| {"8.8.8.8", "8.8.4.4"}, |
| {"2001:4860:4860::8888", "2001:4860:4860::8844"}); |
| EXPECT_CALL(*resolver_, |
| SetNameServers(ElementsAre(StrEq("8.8.8.8"), StrEq("8.8.4.4"), |
| StrEq("2001:4860:4860::8888"), |
| StrEq("2001:4860:4860::8844")))); |
| proxy_->OnDefaultDeviceChanged(dev.get()); |
| } |
| |
| TEST_F(ProxyTest, SystemProxy_ShillPropertyUpdatedOnDefaultServiceComesOnline) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kSystem}); |
| SetNamespaceAddresses(kNetnsPeerIPv4Addr, kNetnsPeerIPv6Addr); |
| |
| auto dev = ShillDevice(shill::Client::Device::ConnectionState::kOnline); |
| EXPECT_CALL(mock_manager_, SetDNSProxyAddresses(_, _, _)) |
| .WillOnce(Return(true)); |
| proxy_->OnDefaultDeviceChanged(dev.get()); |
| } |
| |
| TEST_F(ProxyTest, SystemProxy_IgnoresVPN) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kSystem}); |
| SetNamespaceAddresses(kNetnsPeerIPv4Addr, kNetnsPeerIPv6Addr); |
| |
| // Expect default device changes to WiFi. |
| auto wifi = ShillDevice(shill::Client::Device::ConnectionState::kOnline, |
| shill::Client::Device::Type::kWifi); |
| proxy_->OnDefaultDeviceChanged(wifi.get()); |
| EXPECT_EQ(proxy_->device_->type, shill::Client::Device::Type::kWifi); |
| |
| // Expect default device to still be WiFi even when a VPN is active. |
| auto vpn = ShillDevice(shill::Client::Device::ConnectionState::kOnline, |
| shill::Client::Device::Type::kVPN); |
| proxy_->OnDefaultDeviceChanged(vpn.get()); |
| EXPECT_EQ(proxy_->device_->type, shill::Client::Device::Type::kWifi); |
| } |
| |
| TEST_F(ProxyTest, SystemProxy_GetsPhysicalDeviceOnInitialVPN) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kSystem}); |
| SetNamespaceAddresses(kNetnsPeerIPv4Addr, kNetnsPeerIPv6Addr); |
| |
| shill_client_->default_device_ = |
| ShillDevice(shill::Client::Device::ConnectionState::kOnline, |
| shill::Client::Device::Type::kWifi); |
| |
| auto vpn = ShillDevice(shill::Client::Device::ConnectionState::kOnline, |
| shill::Client::Device::Type::kVPN); |
| proxy_->OnDefaultDeviceChanged(vpn.get()); |
| EXPECT_TRUE(proxy_->device_); |
| EXPECT_EQ(proxy_->device_->type, shill::Client::Device::Type::kWifi); |
| } |
| |
| TEST_F(ProxyTest, DefaultProxy_UsesVPN) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kDefault}); |
| |
| auto wifi = ShillDevice(shill::Client::Device::ConnectionState::kOnline, |
| shill::Client::Device::Type::kWifi); |
| proxy_->OnDefaultDeviceChanged(wifi.get()); |
| EXPECT_TRUE(proxy_->device_); |
| EXPECT_EQ(proxy_->device_->type, shill::Client::Device::Type::kWifi); |
| |
| auto vpn = ShillDevice(shill::Client::Device::ConnectionState::kOnline, |
| shill::Client::Device::Type::kVPN); |
| proxy_->OnDefaultDeviceChanged(vpn.get()); |
| EXPECT_TRUE(proxy_->device_); |
| EXPECT_EQ(proxy_->device_->type, shill::Client::Device::Type::kVPN); |
| } |
| |
| TEST_F(ProxyTest, ArcProxy_NameServersUpdatedOnDeviceChangeEvent) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kARC, .ifname = "wlan0"}); |
| |
| // Set name servers on device change event. |
| auto wifi = ShillDevice(shill::Client::Device::ConnectionState::kOnline, |
| shill::Client::Device::Type::kWifi, "wlan0", |
| {"8.8.8.8"}, {"2001:4860:4860::8888"}); |
| EXPECT_CALL(*resolver_, |
| SetNameServers(ElementsAre(StrEq("8.8.8.8"), |
| StrEq("2001:4860:4860::8888")))); |
| proxy_->OnDeviceChanged(wifi.get()); |
| |
| // Verify it only applies changes for the correct interface. |
| auto eth = ShillDevice(shill::Client::Device::ConnectionState::kOnline, |
| shill::Client::Device::Type::kEthernet, "eth0", |
| {"8.8.8.8", "8.8.4.4"}, |
| {"2001:4860:4860::8888", "2001:4860:4860::8844"}); |
| EXPECT_CALL(*resolver_, SetNameServers(_)).Times(0); |
| proxy_->OnDeviceChanged(eth.get()); |
| |
| // Update WiFi device nameservers. |
| wifi->ipconfig.ipv4_dns_addresses = {"8.8.8.8", "8.8.4.4"}; |
| wifi->ipconfig.ipv6_dns_addresses = {"2001:4860:4860::8888", |
| "2001:4860:4860::8844"}; |
| EXPECT_CALL(*resolver_, |
| SetNameServers(ElementsAre(StrEq("8.8.8.8"), StrEq("8.8.4.4"), |
| StrEq("2001:4860:4860::8888"), |
| StrEq("2001:4860:4860::8844")))); |
| proxy_->OnDeviceChanged(wifi.get()); |
| } |
| |
| TEST_F(ProxyTest, SystemProxy_NameServersUpdatedOnDeviceChangeEvent) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kSystem}); |
| SetNamespaceAddresses(kNetnsPeerIPv4Addr, kNetnsPeerIPv6Addr); |
| |
| // Set name servers on device change event. |
| auto dev = ShillDevice(shill::Client::Device::ConnectionState::kOnline, |
| shill::Client::Device::Type::kEthernet, "eth0", |
| {"8.8.8.8"}, {"2001:4860:4860::8888"}); |
| EXPECT_CALL(*resolver_, |
| SetNameServers(ElementsAre(StrEq("8.8.8.8"), |
| StrEq("2001:4860:4860::8888")))); |
| proxy_->OnDefaultDeviceChanged(dev.get()); |
| |
| // Now trigger an ipconfig change. |
| dev->ipconfig.ipv4_dns_addresses = {"8.8.8.8", "8.8.4.4"}; |
| dev->ipconfig.ipv6_dns_addresses = {"2001:4860:4860::8888", |
| "2001:4860:4860::8844"}; |
| EXPECT_CALL(*resolver_, |
| SetNameServers(ElementsAre(StrEq("8.8.8.8"), StrEq("8.8.4.4"), |
| StrEq("2001:4860:4860::8888"), |
| StrEq("2001:4860:4860::8844")))); |
| proxy_->OnDeviceChanged(dev.get()); |
| } |
| |
| TEST_F(ProxyTest, DeviceChangeEventIgnored) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kSystem}); |
| SetNamespaceAddresses(kNetnsPeerIPv4Addr, kNetnsPeerIPv6Addr); |
| |
| auto dev = ShillDevice(shill::Client::Device::ConnectionState::kOnline, |
| shill::Client::Device::Type::kEthernet, "eth0"); |
| |
| // Set name servers on device change event. |
| EXPECT_CALL(*resolver_, SetNameServers(_)).Times(1); |
| proxy_->OnDefaultDeviceChanged(dev.get()); |
| |
| // No change to ipconfig, no call to SetNameServers |
| EXPECT_CALL(*resolver_, SetNameServers(_)).Times(0); |
| proxy_->OnDeviceChanged(dev.get()); |
| |
| // Different ifname, no call to SetNameServers |
| EXPECT_CALL(*resolver_, SetNameServers(_)).Times(0); |
| dev->ifname = "eth1"; |
| proxy_->OnDeviceChanged(dev.get()); |
| } |
| |
| TEST_F(ProxyTest, BasicDoHDisable) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kSystem}, |
| ShillDevice(shill::Client::Device::ConnectionState::kOnline)); |
| |
| EXPECT_CALL(*resolver_, SetDoHProviders(IsEmpty(), false)); |
| brillo::VariantDictionary props; |
| proxy_->OnDoHProvidersChanged(props); |
| } |
| |
| TEST_F(ProxyTest, BasicDoHAlwaysOn) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kSystem}, |
| ShillDevice(shill::Client::Device::ConnectionState::kOnline)); |
| |
| EXPECT_CALL( |
| *resolver_, |
| SetDoHProviders(ElementsAre(StrEq("https://dns.google.com")), true)); |
| brillo::VariantDictionary props; |
| props["https://dns.google.com"] = std::string(""); |
| proxy_->OnDoHProvidersChanged(props); |
| } |
| |
| TEST_F(ProxyTest, BasicDoHAutomatic) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kSystem}, |
| ShillDevice(shill::Client::Device::ConnectionState::kOnline)); |
| SetNameServers({"8.8.4.4"}, /*ipv6_nameservers=*/{}); |
| |
| EXPECT_CALL( |
| *resolver_, |
| SetDoHProviders(ElementsAre(StrEq("https://dns.google.com")), false)); |
| brillo::VariantDictionary props; |
| props["https://dns.google.com"] = std::string("8.8.8.8, 8.8.4.4"); |
| proxy_->OnDoHProvidersChanged(props); |
| } |
| |
| TEST_F(ProxyTest, RemovesDNSQueryParameterTemplate_AlwaysOn) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kSystem}, |
| ShillDevice(shill::Client::Device::ConnectionState::kOnline)); |
| |
| EXPECT_CALL( |
| *resolver_, |
| SetDoHProviders(ElementsAre(StrEq("https://dns.google.com")), true)); |
| brillo::VariantDictionary props; |
| props["https://dns.google.com{?dns}"] = std::string(""); |
| proxy_->OnDoHProvidersChanged(props); |
| } |
| |
| TEST_F(ProxyTest, RemovesDNSQueryParameterTemplate_Automatic) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kSystem}, |
| ShillDevice(shill::Client::Device::ConnectionState::kOnline)); |
| SetNameServers({"8.8.4.4"}, /*ipv6_nameservers=*/{}); |
| |
| EXPECT_CALL( |
| *resolver_, |
| SetDoHProviders(ElementsAre(StrEq("https://dns.google.com")), false)); |
| brillo::VariantDictionary props; |
| props["https://dns.google.com{?dns}"] = std::string("8.8.8.8, 8.8.4.4"); |
| proxy_->OnDoHProvidersChanged(props); |
| } |
| |
| TEST_F(ProxyTest, NewResolverConfiguredWhenSet) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kSystem}, |
| ShillDevice(shill::Client::Device::ConnectionState::kOnline)); |
| |
| brillo::VariantDictionary props; |
| props["https://dns.google.com"] = std::string("8.8.8.8, 8.8.4.4"); |
| props["https://chrome.cloudflare-dns.com/dns-query"] = |
| std::string("1.1.1.1,2606:4700:4700::1111"); |
| proxy_->OnDoHProvidersChanged(props); |
| |
| SetNameServers({"1.0.0.1", "1.1.1.1"}, /*ipv6_nameservers=*/{}); |
| EXPECT_CALL(*resolver_, SetNameServers(UnorderedElementsAre( |
| StrEq("1.1.1.1"), StrEq("1.0.0.1")))); |
| EXPECT_CALL( |
| *resolver_, |
| SetDoHProviders( |
| ElementsAre(StrEq("https://chrome.cloudflare-dns.com/dns-query")), |
| false)); |
| proxy_->doh_config_.set_resolver(resolver_); |
| } |
| |
| TEST_F(ProxyTest, DoHModeChangingFixedNameServers) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kSystem}, |
| ShillDevice(shill::Client::Device::ConnectionState::kOnline)); |
| |
| // Initially off. |
| EXPECT_CALL(*resolver_, SetDoHProviders(IsEmpty(), false)); |
| SetNameServers({"1.1.1.1", "9.9.9.9"}, /*ipv6_nameservers=*/{}); |
| |
| // Automatic mode - matched cloudflare. |
| EXPECT_CALL( |
| *resolver_, |
| SetDoHProviders( |
| ElementsAre(StrEq("https://chrome.cloudflare-dns.com/dns-query")), |
| false)); |
| brillo::VariantDictionary props; |
| props["https://dns.google.com"] = std::string("8.8.8.8, 8.8.4.4"); |
| props["https://chrome.cloudflare-dns.com/dns-query"] = |
| std::string("1.1.1.1,2606:4700:4700::1111"); |
| proxy_->OnDoHProvidersChanged(props); |
| |
| // Automatic mode - no match. |
| EXPECT_CALL(*resolver_, SetDoHProviders(IsEmpty(), false)); |
| SetNameServers({"10.10.10.1"}, /*ipv6_nameservers=*/{}); |
| |
| // Automatic mode - matched google. |
| EXPECT_CALL( |
| *resolver_, |
| SetDoHProviders(ElementsAre(StrEq("https://dns.google.com")), false)); |
| SetNameServers({"8.8.4.4", "10.10.10.1", "8.8.8.8"}, /*ipv6_nameservers=*/{}); |
| |
| // Explicitly turned off. |
| EXPECT_CALL(*resolver_, SetDoHProviders(IsEmpty(), false)); |
| props.clear(); |
| proxy_->OnDoHProvidersChanged(props); |
| |
| // Still off - even switching ns back. |
| EXPECT_CALL(*resolver_, SetDoHProviders(IsEmpty(), false)); |
| SetNameServers({"8.8.4.4", "10.10.10.1", "8.8.8.8"}, /*ipv6_nameservers=*/{}); |
| |
| // Always-on mode. |
| EXPECT_CALL( |
| *resolver_, |
| SetDoHProviders(ElementsAre(StrEq("https://doh.opendns.com/dns-query")), |
| true)); |
| props.clear(); |
| props["https://doh.opendns.com/dns-query"] = std::string(""); |
| proxy_->OnDoHProvidersChanged(props); |
| |
| // Back to automatic mode, though no matching ns. |
| EXPECT_CALL(*resolver_, SetDoHProviders(IsEmpty(), false)); |
| props.clear(); |
| props["https://doh.opendns.com/dns-query"] = std::string( |
| "208.67.222.222,208.67.220.220,2620:119:35::35, 2620:119:53::53"); |
| proxy_->OnDoHProvidersChanged(props); |
| |
| // Automatic mode working on ns update. |
| EXPECT_CALL( |
| *resolver_, |
| SetDoHProviders(ElementsAre(StrEq("https://doh.opendns.com/dns-query")), |
| false)); |
| SetNameServers({"8.8.8.8"}, {"2620:119:35::35"}); |
| } |
| |
| TEST_F(ProxyTest, MultipleDoHProvidersForAlwaysOnMode) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kSystem}, |
| ShillDevice(shill::Client::Device::ConnectionState::kOnline)); |
| |
| EXPECT_CALL(*resolver_, SetDoHProviders(UnorderedElementsAre( |
| StrEq("https://dns.google.com"), |
| StrEq("https://doh.opendns.com")), |
| true)); |
| brillo::VariantDictionary props; |
| props["https://dns.google.com"] = std::string(""); |
| props["https://doh.opendns.com"] = std::string(""); |
| proxy_->OnDoHProvidersChanged(props); |
| } |
| |
| TEST_F(ProxyTest, MultipleDoHProvidersForAutomaticMode) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kSystem}, |
| ShillDevice(shill::Client::Device::ConnectionState::kOnline)); |
| |
| shill::Client::IPConfig ipconfig; |
| SetNameServers({"1.1.1.1", "10.10.10.10"}, /*ipv6_nameservers=*/{}); |
| |
| EXPECT_CALL( |
| *resolver_, |
| SetDoHProviders( |
| ElementsAre(StrEq("https://chrome.cloudflare-dns.com/dns-query")), |
| false)); |
| brillo::VariantDictionary props; |
| props["https://dns.google.com"] = std::string("8.8.8.8, 8.8.4.4"); |
| props["https://dns.quad9.net/dns-query"] = std::string("9.9.9.9,2620:fe::9"); |
| props["https://chrome.cloudflare-dns.com/dns-query"] = |
| std::string("1.1.1.1,2606:4700:4700::1111"); |
| props["https://doh.opendns.com/dns-query"] = std::string( |
| "208.67.222.222,208.67.220.220,2620:119:35::35, 2620:119:53::53"); |
| proxy_->OnDoHProvidersChanged(props); |
| |
| EXPECT_CALL(*resolver_, |
| SetDoHProviders(UnorderedElementsAre( |
| StrEq("https://dns.google.com"), |
| StrEq("https://doh.opendns.com/dns-query"), |
| StrEq("https://dns.quad9.net/dns-query")), |
| false)); |
| SetNameServers({"8.8.8.8", "10.10.10.10"}, {"2620:fe::9", "2620:119:53::53"}); |
| } |
| |
| TEST_F(ProxyTest, DoHBadAlwaysOnConfigSetsAutomaticMode) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kSystem}, |
| ShillDevice(shill::Client::Device::ConnectionState::kOnline)); |
| SetNameServers({"1.1.1.1", "10.10.10.10"}, /*ipv6_nameservers=*/{}); |
| |
| EXPECT_CALL( |
| *resolver_, |
| SetDoHProviders( |
| ElementsAre(StrEq("https://chrome.cloudflare-dns.com/dns-query")), |
| false)); |
| brillo::VariantDictionary props; |
| props["https://dns.opendns.com"] = std::string(""); |
| props["https://dns.google.com"] = std::string("8.8.8.8, 8.8.4.4"); |
| props["https://dns.quad9.net/dns-query"] = std::string("9.9.9.9,2620:fe::9"); |
| props["https://chrome.cloudflare-dns.com/dns-query"] = |
| std::string("1.1.1.1,2606:4700:4700::1111"); |
| props["https://doh.opendns.com/dns-query"] = std::string( |
| "208.67.222.222,208.67.220.220,2620:119:35::35, 2620:119:53::53"); |
| proxy_->OnDoHProvidersChanged(props); |
| |
| EXPECT_CALL(*resolver_, |
| SetDoHProviders(UnorderedElementsAre( |
| StrEq("https://dns.google.com"), |
| StrEq("https://doh.opendns.com/dns-query"), |
| StrEq("https://dns.quad9.net/dns-query")), |
| false)); |
| SetNameServers({"8.8.8.8", "10.10.10.10"}, {"2620:fe::9", "2620:119:53::53"}); |
| } |
| |
| TEST_F(ProxyTest, DefaultProxy_DisableDoHProvidersOnVPN) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kDefault}); |
| proxy_->device_ = ShillDevice(shill::Client::Device::ConnectionState::kOnline, |
| shill::Client::Device::Type::kVPN); |
| |
| EXPECT_CALL(*resolver_, SetDoHProviders(IsEmpty(), false)); |
| brillo::VariantDictionary props; |
| props["https://dns.google.com"] = std::string(""); |
| props["https://doh.opendns.com"] = std::string(""); |
| proxy_->OnDoHProvidersChanged(props); |
| } |
| |
| TEST_F(ProxyTest, SystemProxy_SetsDnsRedirectionRule) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kSystem}); |
| SetNamespaceAddresses(kNetnsPeerIPv4Addr, kNetnsPeerIPv6Addr); |
| |
| // System proxy requests a DnsRedirectionRule to exclude traffic destined |
| // not to the underlying network's name server. |
| auto dev = ShillDevice(shill::Client::Device::ConnectionState::kOnline, |
| shill::Client::Device::Type::kEthernet, "eth0"); |
| EXPECT_CALL( |
| *patchpanel_client_, |
| RedirectDns( |
| patchpanel::Client::DnsRedirectionRequestType::kExcludeDestination, _, |
| kNetnsPeerIPv4Addr.ToString(), _, _)) |
| .WillOnce(Return(ByMove(base::ScopedFD(make_fd())))); |
| EXPECT_CALL( |
| *patchpanel_client_, |
| RedirectDns( |
| patchpanel::Client::DnsRedirectionRequestType::kExcludeDestination, _, |
| kNetnsPeerIPv6Addr.ToString(), _, _)) |
| .WillOnce(Return(ByMove(base::ScopedFD(make_fd())))); |
| proxy_->OnDefaultDeviceChanged(dev.get()); |
| |
| // System proxy does not call patchpanel on Parallels VM started. |
| EXPECT_CALL(*patchpanel_client_, RedirectDns(_, _, _, _, _)).Times(0); |
| proxy_->OnVirtualDeviceChanged( |
| patchpanel::Client::VirtualDeviceEvent::kAdded, |
| virtualdev(patchpanel::Client::GuestType::kParallelsVm, "vmtap1", |
| "eth0")); |
| |
| // System proxy does not call patchpanel on ARC started. |
| EXPECT_CALL(*patchpanel_client_, RedirectDns(_, _, _, _, _)).Times(0); |
| proxy_->OnVirtualDeviceChanged( |
| patchpanel::Client::VirtualDeviceEvent::kAdded, |
| virtualdev(patchpanel::Client::GuestType::kArcContainer, "arc_eth0", |
| "eth0")); |
| } |
| |
| TEST_F(ProxyTest, DefaultProxy_SetDnsRedirectionRuleDeviceAlreadyStarted) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kDefault}, ShillDevice()); |
| SetNameServers({"8.8.8.8"}, {"2001:4860:4860::8888"}); |
| SetNamespaceAddresses(kNetnsPeerIPv4Addr, kNetnsPeerIPv6Addr); |
| |
| // Set DNS redirection rule. |
| EXPECT_CALL(*patchpanel_client_, |
| RedirectDns(patchpanel::Client::DnsRedirectionRequestType::kUser, |
| _, _, ElementsAre("8.8.8.8"), _)) |
| .WillOnce(Return(ByMove(base::ScopedFD(make_fd())))); |
| EXPECT_CALL(*patchpanel_client_, |
| RedirectDns(patchpanel::Client::DnsRedirectionRequestType::kUser, |
| _, _, ElementsAre("2001:4860:4860::8888"), _)) |
| .WillOnce(Return(ByMove(base::ScopedFD(make_fd())))); |
| proxy_->Enable(); |
| EXPECT_EQ(proxy_->lifeline_fds_.size(), 2); |
| } |
| |
| TEST_F(ProxyTest, DefaultProxy_SetDnsRedirectionRuleNewDeviceStarted) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kDefault}); |
| SetNamespaceAddresses(kNetnsPeerIPv4Addr, kNetnsPeerIPv6Addr); |
| |
| // Empty active device. |
| EXPECT_CALL(*patchpanel_client_, RedirectDns(_, _, _, _, _)).Times(0); |
| proxy_->Enable(); |
| EXPECT_EQ(proxy_->lifeline_fds_.size(), 0); |
| |
| // Default device changed. |
| auto dev = ShillDevice(shill::Client::Device::ConnectionState::kOnline, |
| shill::Client::Device::Type::kEthernet, "eth0", |
| {"8.8.8.8"}, {"2001:4860:4860::8888"}); |
| EXPECT_CALL(*patchpanel_client_, |
| RedirectDns(patchpanel::Client::DnsRedirectionRequestType::kUser, |
| _, _, ElementsAre("8.8.8.8"), _)) |
| .WillOnce(Return(ByMove(base::ScopedFD(make_fd())))); |
| EXPECT_CALL(*patchpanel_client_, |
| RedirectDns(patchpanel::Client::DnsRedirectionRequestType::kUser, |
| _, _, ElementsAre("2001:4860:4860::8888"), _)) |
| .WillOnce(Return(ByMove(base::ScopedFD(make_fd())))); |
| proxy_->OnDefaultDeviceChanged(dev.get()); |
| EXPECT_EQ(proxy_->lifeline_fds_.size(), 2); |
| } |
| |
| TEST_F(ProxyTest, DefaultProxy_SetDnsRedirectionRuleGuest) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kDefault}, |
| ShillDevice(shill::Client::Device::ConnectionState::kOnline, |
| shill::Client::Device::Type::kEthernet, "eth0")); |
| SetNamespaceAddresses(kNetnsPeerIPv4Addr, kNetnsPeerIPv6Addr); |
| |
| // Guest started. |
| auto plugin_vm_dev = |
| virtualdev(patchpanel::Client::GuestType::kParallelsVm, "vmtap0", "eth0"); |
| EXPECT_CALL( |
| *patchpanel_client_, |
| RedirectDns(patchpanel::Client::DnsRedirectionRequestType::kDefault, |
| "vmtap0", kNetnsPeerIPv4Addr.ToString(), IsEmpty(), _)) |
| .WillOnce(Return(ByMove(base::ScopedFD(make_fd())))); |
| EXPECT_CALL( |
| *patchpanel_client_, |
| RedirectDns(patchpanel::Client::DnsRedirectionRequestType::kDefault, |
| "vmtap0", kNetnsPeerIPv6Addr.ToString(), IsEmpty(), _)) |
| .WillOnce(Return(ByMove(base::ScopedFD(make_fd())))); |
| proxy_->OnVirtualDeviceChanged(patchpanel::Client::VirtualDeviceEvent::kAdded, |
| plugin_vm_dev); |
| EXPECT_EQ(proxy_->lifeline_fds_.size(), 2); |
| |
| // Guest stopped. |
| proxy_->OnVirtualDeviceChanged( |
| patchpanel::Client::VirtualDeviceEvent::kRemoved, plugin_vm_dev); |
| EXPECT_EQ(proxy_->lifeline_fds_.size(), 0); |
| } |
| |
| TEST_F(ProxyTest, DefaultProxy_NeverSetsDnsRedirectionRuleOtherGuest) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kDefault}, |
| ShillDevice(shill::Client::Device::ConnectionState::kOnline, |
| shill::Client::Device::Type::kEthernet, "eth0")); |
| SetNamespaceAddresses(kNetnsPeerIPv4Addr, kNetnsPeerIPv6Addr); |
| |
| // Other guest started. |
| EXPECT_CALL(*patchpanel_client_, RedirectDns(_, _, _, _, _)).Times(0); |
| proxy_->OnVirtualDeviceChanged( |
| patchpanel::Client::VirtualDeviceEvent::kAdded, |
| virtualdev(patchpanel::Client::GuestType::kArcContainer, "arc_eth0", |
| "eth0")); |
| } |
| |
| TEST_F(ProxyTest, SystemProxy_SetDnsRedirectionRuleIPv6Added) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kSystem}, ShillDevice()); |
| SetNameServers({"8.8.8.8"}, {"2001:4860:4860::8888"}); |
| SetNamespaceAddresses(kNetnsPeerIPv4Addr, /*ipv6_addr=*/std::nullopt); |
| |
| EXPECT_CALL( |
| *patchpanel_client_, |
| RedirectDns( |
| patchpanel::Client::DnsRedirectionRequestType::kExcludeDestination, _, |
| kNetnsPeerIPv6Addr.ToString(), _, _)) |
| .WillOnce(Return(ByMove(base::ScopedFD(make_fd())))); |
| |
| // Proxy's ConnectedNamespace peer interface name is set to empty and |
| // RTNL message's interface index is set to 0 in order to match. |
| // if_nametoindex which is used to get the interface index will return 0 on |
| // error. |
| net_base::RTNLMessage msg(net_base::RTNLMessage::kTypeAddress, |
| net_base::RTNLMessage::kModeAdd, 0 /* flags */, |
| 0 /* seq */, 0 /* pid */, 0 /* interface_index */, |
| AF_INET6); |
| msg.set_address_status( |
| net_base::RTNLMessage::AddressStatus(0, 0, RT_SCOPE_UNIVERSE)); |
| msg.SetAttribute(IFA_ADDRESS, kNetnsPeerIPv6Addr.ToBytes()); |
| proxy_->RTNLMessageHandler(msg); |
| } |
| |
| TEST_F(ProxyTest, SystemProxy_SetDnsRedirectionRuleIPv6Deleted) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kSystem}, ShillDevice()); |
| |
| proxy_->lifeline_fds_.emplace(std::make_pair("", AF_INET6), |
| base::ScopedFD(make_fd())); |
| |
| net_base::RTNLMessage msg(net_base::RTNLMessage::kTypeAddress, |
| net_base::RTNLMessage::kModeDelete, 0 /* flags */, |
| 0 /* seq */, 0 /* pid */, 0 /* interface_index */, |
| AF_INET6); |
| msg.set_address_status( |
| net_base::RTNLMessage::AddressStatus(0, 0, RT_SCOPE_UNIVERSE)); |
| proxy_->RTNLMessageHandler(msg); |
| EXPECT_EQ(proxy_->lifeline_fds_.size(), 0); |
| } |
| |
| TEST_F(ProxyTest, DefaultProxy_SetDnsRedirectionRuleWithoutIPv6) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kDefault}); |
| SetNamespaceAddresses(kNetnsPeerIPv4Addr, /*ipv6_addr=*/std::nullopt); |
| |
| // Default device changed. |
| auto dev = ShillDevice(shill::Client::Device::ConnectionState::kOnline, |
| shill::Client::Device::Type::kEthernet, "eth0", |
| {"8.8.8.8"}, {"2001:4860:4860::8888"}); |
| EXPECT_CALL(*patchpanel_client_, |
| RedirectDns(patchpanel::Client::DnsRedirectionRequestType::kUser, |
| _, _, ElementsAre("8.8.8.8"), _)) |
| .WillOnce(Return(ByMove(base::ScopedFD(make_fd())))); |
| proxy_->OnDefaultDeviceChanged(dev.get()); |
| EXPECT_EQ(proxy_->lifeline_fds_.size(), 1); |
| |
| // Guest started. |
| auto plugin_vm_dev = |
| virtualdev(patchpanel::Client::GuestType::kParallelsVm, "vmtap0", "eth0"); |
| EXPECT_CALL( |
| *patchpanel_client_, |
| RedirectDns(patchpanel::Client::DnsRedirectionRequestType::kDefault, |
| "vmtap0", kNetnsPeerIPv4Addr.ToString(), IsEmpty(), _)) |
| .WillOnce(Return(ByMove(base::ScopedFD(make_fd())))); |
| proxy_->OnVirtualDeviceChanged(patchpanel::Client::VirtualDeviceEvent::kAdded, |
| plugin_vm_dev); |
| EXPECT_EQ(proxy_->lifeline_fds_.size(), 2); |
| |
| // Guest stopped. |
| proxy_->OnVirtualDeviceChanged( |
| patchpanel::Client::VirtualDeviceEvent::kRemoved, plugin_vm_dev); |
| EXPECT_EQ(proxy_->lifeline_fds_.size(), 1); |
| } |
| |
| TEST_F(ProxyTest, DefaultProxy_SetDnsRedirectionRuleIPv6Added) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kDefault}, ShillDevice()); |
| SetNameServers({"8.8.8.8"}, {"2001:4860:4860::8888"}); |
| SetNamespaceAddresses(kNetnsPeerIPv4Addr, /*ipv6_addr=*/std::nullopt); |
| |
| EXPECT_CALL(*patchpanel_client_, GetDevices()) |
| .WillOnce( |
| Return(std::vector<patchpanel::Client::VirtualDevice>{virtualdev( |
| patchpanel::Client::GuestType::kTerminaVm, "vmtap0", "eth0")})); |
| EXPECT_CALL(*patchpanel_client_, |
| RedirectDns(patchpanel::Client::DnsRedirectionRequestType::kUser, |
| _, kNetnsPeerIPv6Addr.ToString(), _, _)) |
| .WillOnce(Return(ByMove(base::ScopedFD(make_fd())))); |
| EXPECT_CALL( |
| *patchpanel_client_, |
| RedirectDns(patchpanel::Client::DnsRedirectionRequestType::kDefault, |
| "vmtap0", kNetnsPeerIPv6Addr.ToString(), IsEmpty(), _)) |
| .WillOnce(Return(ByMove(base::ScopedFD(make_fd())))); |
| |
| // Proxy's ConnectedNamespace peer interface name is set to empty and |
| // RTNL message's interface index is set to 0 in order to match. |
| // if_nametoindex which is used to get the interface index will return 0 on |
| // error. |
| net_base::RTNLMessage msg(net_base::RTNLMessage::kTypeAddress, |
| net_base::RTNLMessage::kModeAdd, 0 /* flags */, |
| 0 /* seq */, 0 /* pid */, 0 /* interface_index */, |
| AF_INET6); |
| msg.set_address_status( |
| net_base::RTNLMessage::AddressStatus(0, 0, RT_SCOPE_UNIVERSE)); |
| msg.SetAttribute(IFA_ADDRESS, kNetnsPeerIPv6Addr.ToBytes()); |
| proxy_->RTNLMessageHandler(msg); |
| } |
| |
| TEST_F(ProxyTest, DefaultProxy_SetDnsRedirectionRuleIPv6Deleted) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kDefault}, ShillDevice()); |
| |
| proxy_->lifeline_fds_.emplace(std::make_pair("", AF_INET6), |
| base::ScopedFD(make_fd())); |
| proxy_->lifeline_fds_.emplace(std::make_pair("vmtap0", AF_INET6), |
| base::ScopedFD(make_fd())); |
| |
| EXPECT_CALL(*patchpanel_client_, GetDevices()) |
| .WillOnce( |
| Return(std::vector<patchpanel::Client::VirtualDevice>{virtualdev( |
| patchpanel::Client::GuestType::kTerminaVm, "vmtap0", "eth0")})); |
| |
| net_base::RTNLMessage msg(net_base::RTNLMessage::kTypeAddress, |
| net_base::RTNLMessage::kModeDelete, 0 /* flags */, |
| 0 /* seq */, 0 /* pid */, 0 /* interface_index */, |
| AF_INET6); |
| msg.set_address_status( |
| net_base::RTNLMessage::AddressStatus(0, 0, RT_SCOPE_UNIVERSE)); |
| proxy_->RTNLMessageHandler(msg); |
| EXPECT_EQ(proxy_->lifeline_fds_.size(), 0); |
| } |
| |
| TEST_F(ProxyTest, DefaultProxy_SetDnsRedirectionRuleUnrelatedIPv6Added) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kDefault}, ShillDevice()); |
| |
| EXPECT_CALL(*patchpanel_client_, GetDevices()) |
| .WillRepeatedly( |
| Return(std::vector<patchpanel::Client::VirtualDevice>{virtualdev( |
| patchpanel::Client::GuestType::kTerminaVm, "vmtap0", "eth0")})); |
| EXPECT_CALL(*patchpanel_client_, RedirectDns(_, _, _, _, _)).Times(0); |
| |
| // Proxy's ConnectedNamespace peer interface name is set to empty and |
| // RTNL message's interface index is set to -1 in order to not match. |
| // if_nametoindex which is used to get the interface index will return 0 on |
| // error. |
| net_base::RTNLMessage msg_unrelated_ifindex( |
| net_base::RTNLMessage::kTypeAddress, net_base::RTNLMessage::kModeAdd, |
| 0 /* flags */, 0 /* seq */, 0 /* pid */, -1 /* interface_index */, |
| AF_INET6); |
| msg_unrelated_ifindex.set_address_status( |
| net_base::RTNLMessage::AddressStatus(0, 0, RT_SCOPE_UNIVERSE)); |
| msg_unrelated_ifindex.SetAttribute(IFA_ADDRESS, kNetnsPeerIPv6Addr.ToBytes()); |
| proxy_->RTNLMessageHandler(msg_unrelated_ifindex); |
| |
| net_base::RTNLMessage msg_unrelated_scope( |
| net_base::RTNLMessage::kTypeAddress, net_base::RTNLMessage::kModeAdd, |
| 0 /* flags */, 0 /* seq */, 0 /* pid */, -1 /* interface_index */, |
| AF_INET6); |
| msg_unrelated_scope.set_address_status( |
| net_base::RTNLMessage::AddressStatus(0, 0, RT_SCOPE_LINK)); |
| msg_unrelated_scope.SetAttribute(IFA_ADDRESS, kNetnsPeerIPv6Addr.ToBytes()); |
| proxy_->RTNLMessageHandler(msg_unrelated_scope); |
| } |
| |
| TEST_F(ProxyTest, ArcProxy_SetDnsRedirectionRuleDeviceAlreadyStarted) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kARC, .ifname = "eth0"}, |
| ShillDevice()); |
| SetNamespaceAddresses(kNetnsPeerIPv4Addr, kNetnsPeerIPv6Addr); |
| |
| // Set devices created before the proxy started. |
| EXPECT_CALL(*patchpanel_client_, GetDevices()) |
| .WillOnce( |
| Return(std::vector<patchpanel::Client::VirtualDevice>{virtualdev( |
| patchpanel::Client::GuestType::kArcVm, "arc_eth0", "eth0")})); |
| EXPECT_CALL( |
| *patchpanel_client_, |
| RedirectDns(patchpanel::Client::DnsRedirectionRequestType::kArc, |
| "arc_eth0", kNetnsPeerIPv4Addr.ToString(), IsEmpty(), _)) |
| .WillOnce(Return(ByMove(base::ScopedFD(make_fd())))); |
| EXPECT_CALL( |
| *patchpanel_client_, |
| RedirectDns(patchpanel::Client::DnsRedirectionRequestType::kArc, |
| "arc_eth0", kNetnsPeerIPv6Addr.ToString(), IsEmpty(), _)) |
| .WillOnce(Return(ByMove(base::ScopedFD(make_fd())))); |
| proxy_->Enable(); |
| EXPECT_EQ(proxy_->lifeline_fds_.size(), 2); |
| } |
| |
| TEST_F(ProxyTest, ArcProxy_SetDnsRedirectionRuleNewDeviceStarted) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kARC, .ifname = "eth0"}, |
| ShillDevice()); |
| SetNamespaceAddresses(kNetnsPeerIPv4Addr, kNetnsPeerIPv6Addr); |
| |
| // Guest started. |
| auto arc_dev = virtualdev(patchpanel::Client::GuestType::kArcContainer, |
| "arc_eth0", "eth0"); |
| EXPECT_CALL( |
| *patchpanel_client_, |
| RedirectDns(patchpanel::Client::DnsRedirectionRequestType::kArc, |
| "arc_eth0", kNetnsPeerIPv4Addr.ToString(), IsEmpty(), _)) |
| .WillOnce(Return(ByMove(base::ScopedFD(make_fd())))); |
| EXPECT_CALL( |
| *patchpanel_client_, |
| RedirectDns(patchpanel::Client::DnsRedirectionRequestType::kArc, |
| "arc_eth0", kNetnsPeerIPv6Addr.ToString(), IsEmpty(), _)) |
| .WillOnce(Return(ByMove(base::ScopedFD(make_fd())))); |
| proxy_->OnVirtualDeviceChanged(patchpanel::Client::VirtualDeviceEvent::kAdded, |
| arc_dev); |
| EXPECT_EQ(proxy_->lifeline_fds_.size(), 2); |
| |
| // Guest stopped. |
| proxy_->OnVirtualDeviceChanged( |
| patchpanel::Client::VirtualDeviceEvent::kRemoved, arc_dev); |
| EXPECT_EQ(proxy_->lifeline_fds_.size(), 0); |
| } |
| |
| TEST_F(ProxyTest, ArcProxy_NeverSetsDnsRedirectionRuleOtherGuest) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kARC, .ifname = "eth0"}, |
| ShillDevice()); |
| proxy_->ns_peer_ipv6_address_ = kNetnsPeerIPv6Addr; |
| |
| // Other guest started. |
| EXPECT_CALL(*patchpanel_client_, RedirectDns(_, _, _, _, _)).Times(0); |
| proxy_->OnVirtualDeviceChanged( |
| patchpanel::Client::VirtualDeviceEvent::kAdded, |
| virtualdev(patchpanel::Client::GuestType::kTerminaVm, "vmtap0", "eth0")); |
| } |
| |
| TEST_F(ProxyTest, ArcProxy_NeverSetsDnsRedirectionRuleOtherIfname) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kARC, .ifname = "wlan0"}); |
| proxy_->device_ = ShillDevice(); |
| SetNamespaceAddresses(kNetnsPeerIPv4Addr, kNetnsPeerIPv6Addr); |
| |
| // ARC guest with other interface started. |
| EXPECT_CALL(*patchpanel_client_, RedirectDns(_, _, _, _, _)).Times(0); |
| proxy_->OnVirtualDeviceChanged( |
| patchpanel::Client::VirtualDeviceEvent::kAdded, |
| virtualdev(patchpanel::Client::GuestType::kArcVm, "arc_eth0", "eth0")); |
| } |
| |
| TEST_F(ProxyTest, ArcProxy_SetDnsRedirectionRuleIPv6Added) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kARC, .ifname = "eth0"}, |
| ShillDevice()); |
| SetNamespaceAddresses(kNetnsPeerIPv4Addr, /*ipv6_addr=*/std::nullopt); |
| |
| EXPECT_CALL(*patchpanel_client_, GetDevices()) |
| .WillOnce( |
| Return(std::vector<patchpanel::Client::VirtualDevice>{virtualdev( |
| patchpanel::Client::GuestType::kArcVm, "arc_eth0", "eth0")})); |
| EXPECT_CALL( |
| *patchpanel_client_, |
| RedirectDns(patchpanel::Client::DnsRedirectionRequestType::kArc, |
| "arc_eth0", kNetnsPeerIPv6Addr.ToString(), IsEmpty(), _)) |
| .WillOnce(Return(ByMove(base::ScopedFD(make_fd())))); |
| |
| // Proxy's ConnectedNamespace peer interface name is set to empty and |
| // RTNL message's interface index is set to 0 in order to match. |
| // if_nametoindex which is used to get the interface index will return 0 on |
| // error. |
| net_base::RTNLMessage msg(net_base::RTNLMessage::kTypeAddress, |
| net_base::RTNLMessage::kModeAdd, 0 /* flags */, |
| 0 /* seq */, 0 /* pid */, 0 /* interface_index */, |
| AF_INET6); |
| msg.set_address_status( |
| net_base::RTNLMessage::AddressStatus(0, 0, RT_SCOPE_UNIVERSE)); |
| msg.SetAttribute(IFA_ADDRESS, kNetnsPeerIPv6Addr.ToBytes()); |
| proxy_->RTNLMessageHandler(msg); |
| } |
| |
| TEST_F(ProxyTest, ArcProxy_SetDnsRedirectionRuleIPv6Deleted) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kARC, .ifname = "eth0"}, |
| ShillDevice()); |
| |
| proxy_->lifeline_fds_.emplace(std::make_pair("arc_eth0", AF_INET6), |
| base::ScopedFD(make_fd())); |
| |
| EXPECT_CALL(*patchpanel_client_, GetDevices()) |
| .WillOnce( |
| Return(std::vector<patchpanel::Client::VirtualDevice>{virtualdev( |
| patchpanel::Client::GuestType::kArcVm, "arc_eth0", "eth0")})); |
| |
| net_base::RTNLMessage msg(net_base::RTNLMessage::kTypeAddress, |
| net_base::RTNLMessage::kModeDelete, 0 /* flags */, |
| 0 /* seq */, 0 /* pid */, 0 /* interface_index */, |
| AF_INET6); |
| msg.set_address_status( |
| net_base::RTNLMessage::AddressStatus(0, 0, RT_SCOPE_UNIVERSE)); |
| proxy_->RTNLMessageHandler(msg); |
| EXPECT_EQ(proxy_->lifeline_fds_.size(), 0); |
| } |
| |
| TEST_F(ProxyTest, ArcProxy_SetDnsRedirectionRuleUnrelatedIPv6Added) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kARC, .ifname = "eth0"}, |
| ShillDevice()); |
| |
| EXPECT_CALL(*patchpanel_client_, GetDevices()) |
| .WillRepeatedly( |
| Return(std::vector<patchpanel::Client::VirtualDevice>{virtualdev( |
| patchpanel::Client::GuestType::kArcVm, "arc_eth0", "eth0")})); |
| EXPECT_CALL(*patchpanel_client_, RedirectDns(_, _, _, _, _)).Times(0); |
| |
| // Proxy's ConnectedNamespace peer interface name is set to empty and |
| // RTNL message's interface index is set to -1 in order to not match. |
| // if_nametoindex which is used to get the interface index will return 0 on |
| // error. |
| net_base::RTNLMessage msg_unrelated_ifindex( |
| net_base::RTNLMessage::kTypeAddress, net_base::RTNLMessage::kModeAdd, |
| 0 /* flags */, 0 /* seq */, 0 /* pid */, -1 /* interface_index */, |
| AF_INET6); |
| msg_unrelated_ifindex.set_address_status( |
| net_base::RTNLMessage::AddressStatus(0, 0, RT_SCOPE_UNIVERSE)); |
| msg_unrelated_ifindex.SetAttribute(IFA_ADDRESS, kNetnsPeerIPv6Addr.ToBytes()); |
| proxy_->RTNLMessageHandler(msg_unrelated_ifindex); |
| |
| net_base::RTNLMessage msg_unrelated_scope( |
| net_base::RTNLMessage::kTypeAddress, net_base::RTNLMessage::kModeAdd, |
| 0 /* flags */, 0 /* seq */, 0 /* pid */, -1 /* interface_index */, |
| AF_INET6); |
| msg_unrelated_scope.set_address_status( |
| net_base::RTNLMessage::AddressStatus(0, 0, RT_SCOPE_LINK)); |
| msg_unrelated_scope.SetAttribute(IFA_ADDRESS, kNetnsPeerIPv6Addr.ToBytes()); |
| proxy_->RTNLMessageHandler(msg_unrelated_scope); |
| } |
| |
| TEST_F(ProxyTest, UpdateNameServers) { |
| SetUpProxy(Proxy::Options{.type = Proxy::Type::kSystem}, ShillDevice()); |
| proxy_->device_->ipconfig.ipv4_dns_addresses = { |
| // Valid IPv4 name servers. |
| "8.8.8.8", "192.168.1.1", |
| // Valid IPv6 name servers inside IPv4 config. |
| // Expected to be propagated to DNS proxy's |
| // IPv6 name servers. |
| "eeb0:117e:92ee:ad3d:ce0d:a646:95ea:a16d", "::1", |
| // Ignored invalid name servers. |
| "256.256.256.256", "0.0.0.0", "::", "a", ""}; |
| proxy_->device_->ipconfig.ipv6_dns_addresses = { |
| // Ignored valid IPv4 name servers. |
| "8.8.4.4", "192.168.1.2", |
| // Valid IPv6 name servers. |
| "eeb0:117e:92ee:ad3d:ce0d:a646:95ea:a16e", "::2", |
| // Ignored invalid name servers. |
| "256.256.256.257", "0.0.0.0", "::", "b", ""}; |
| proxy_->UpdateNameServers(); |
| |
| const std::vector<net_base::IPv4Address> expected_ipv4_dns_addresses = { |
| net_base::IPv4Address(8, 8, 8, 8), net_base::IPv4Address(192, 168, 1, 1)}; |
| const std::vector<net_base::IPv6Address> expected_ipv6_dns_addresses = { |
| *net_base::IPv6Address::CreateFromString( |
| "eeb0:117e:92ee:ad3d:ce0d:a646:95ea:a16d"), |
| *net_base::IPv6Address::CreateFromString("::1"), |
| *net_base::IPv6Address::CreateFromString( |
| "eeb0:117e:92ee:ad3d:ce0d:a646:95ea:a16e"), |
| *net_base::IPv6Address::CreateFromString("::2")}; |
| |
| EXPECT_THAT(proxy_->doh_config_.ipv4_nameservers(), |
| expected_ipv4_dns_addresses); |
| EXPECT_THAT(proxy_->doh_config_.ipv6_nameservers(), |
| expected_ipv6_dns_addresses); |
| } |
| } // namespace dns_proxy |