blob: 68627567a41bc6dd8a42df60770b723d68251f95 [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/connectivity/network/mdns/service/agents/service_instance_resolver.h"
#include <lib/zx/time.h>
#include <gtest/gtest.h>
#include "src/connectivity/network/mdns/service/common/mdns_names.h"
#include "src/connectivity/network/mdns/service/common/service_instance.h"
#include "src/connectivity/network/mdns/service/common/type_converters.h"
#include "src/connectivity/network/mdns/service/test/agent_test.h"
namespace fuchsia::net {
bool operator==(const fuchsia::net::SocketAddress& lhs, const fuchsia::net::SocketAddress& rhs) {
if (lhs.Which() != rhs.Which()) {
return false;
}
if (lhs.is_ipv4()) {
return lhs.ipv4().address.addr == rhs.ipv4().address.addr && lhs.ipv4().port == rhs.ipv4().port;
} else {
return lhs.ipv6().address.addr == rhs.ipv6().address.addr &&
lhs.ipv6().port == rhs.ipv6().port && lhs.ipv6().zone_index == rhs.ipv6().zone_index;
}
}
} // namespace fuchsia::net
namespace mdns {
namespace test {
class InstanceResolverTest : public AgentTest {
public:
InstanceResolverTest() = default;
protected:
void ReceivePublication(ServiceInstanceResolver& under_test, const std::string& host_full_name,
const std::string& service_name, const std::string& instance_name,
inet::IpPort port, const std::vector<std::vector<uint8_t>>& text,
ReplyAddress sender_address, bool include_address) {
auto service_full_name = MdnsNames::ServiceFullName(service_name);
auto instance_full_name = MdnsNames::InstanceFullName(instance_name, service_name);
DnsResource ptr_resource(service_full_name, DnsType::kPtr);
ptr_resource.ptr_.pointer_domain_name_ = DnsName(instance_full_name);
under_test.ReceiveResource(ptr_resource, MdnsResourceSection::kAnswer, sender_address);
DnsResource srv_resource(instance_full_name, DnsType::kSrv);
srv_resource.srv_.port_ = port;
srv_resource.srv_.target_ = DnsName(host_full_name);
under_test.ReceiveResource(srv_resource, MdnsResourceSection::kAnswer, sender_address);
DnsResource txt_resource(instance_full_name, DnsType::kTxt);
txt_resource.txt_.strings_ = text;
under_test.ReceiveResource(txt_resource, MdnsResourceSection::kAnswer, sender_address);
if (include_address) {
DnsResource a_resource(host_full_name, sender_address.socket_address().address());
under_test.ReceiveResource(a_resource, MdnsResourceSection::kAnswer, sender_address);
}
under_test.EndOfMessage();
}
void ReceiveAddress(ServiceInstanceResolver& under_test, const std::string& host_full_name,
ReplyAddress sender_address) {
DnsResource a_resource(host_full_name, sender_address.socket_address().address());
under_test.ReceiveResource(a_resource, MdnsResourceSection::kAnswer, sender_address);
under_test.EndOfMessage();
}
};
constexpr char kHostName[] = "test2host";
constexpr char kHostFullName[] = "test2host.local.";
constexpr char kServiceName[] = "_testservice._tcp.";
constexpr char kInstanceName[] = "testinstance";
const inet::IpPort kPort = inet::IpPort::From_uint16_t(1234);
const std::vector<std::vector<uint8_t>> kText = fidl::To<std::vector<std::vector<uint8_t>>>(
std::vector<std::string>{"color=red", "shape=round"});
constexpr bool kIncludeLocal = true;
constexpr bool kExcludeLocal = false;
constexpr bool kIncludeLocalProxies = true;
constexpr bool kExcludeLocalProxies = false;
constexpr bool kFromLocalProxyHost = true;
constexpr bool kFromLocalHost = false;
const std::vector<HostAddress> kHostAddresses{
HostAddress(inet::IpAddress(192, 168, 1, 200), 1, zx::sec(450)),
HostAddress(inet::IpAddress(0xfe80, 200), 1, zx::sec(450))};
const std::vector<inet::SocketAddress> kSocketAddresses{
inet::SocketAddress(inet::IpAddress(192, 168, 1, 200), kPort, 1),
inet::SocketAddress(inet::IpAddress(0xfe80, 200), kPort, 1)};
constexpr zx::duration kAdditionalInterval = zx::sec(1);
constexpr uint32_t kAdditionalIntervalMultiplier = 2;
constexpr uint32_t kAdditionalMaxQueries = 3;
// Tests the behavior of the resolver when configured to discover services on the local host.
TEST_F(InstanceResolverTest, LocalInstance) {
fuchsia::net::mdns::ServiceInstance instance_from_callback;
bool callback_called = false;
ServiceInstanceResolver under_test(
this, kServiceName, kInstanceName, now(), Media::kBoth, IpVersions::kBoth, kIncludeLocal,
kExcludeLocalProxies,
[&instance_from_callback, &callback_called](fuchsia::net::mdns::ServiceInstance instance) {
instance_from_callback = std::move(instance);
callback_called = true;
});
SetAgent(under_test);
SetLocalHostAddresses(kHostAddresses);
under_test.Start(kLocalHostFullName);
// Expect a SRV and TXT questions on start.
ExpectQueryCall(DnsType::kSrv, MdnsNames::InstanceFullName(kInstanceName, kServiceName),
Media::kBoth, IpVersions::kBoth, now(), kAdditionalInterval,
kAdditionalIntervalMultiplier, kAdditionalMaxQueries, true);
ExpectQueryCall(DnsType::kTxt, MdnsNames::InstanceFullName(kInstanceName, kServiceName),
Media::kBoth, IpVersions::kBoth, now(), kAdditionalInterval,
kAdditionalIntervalMultiplier, kAdditionalMaxQueries, true);
ExpectPostTaskForTime(zx::sec(0), zx::sec(0));
ExpectNoOther();
under_test.OnAddLocalServiceInstance(
ServiceInstance(kServiceName, kInstanceName, kLocalHostName, kSocketAddresses, kText),
kFromLocalHost);
EXPECT_TRUE(callback_called);
EXPECT_EQ(kServiceName, instance_from_callback.service());
EXPECT_EQ(kInstanceName, instance_from_callback.instance());
EXPECT_EQ(kLocalHostName, instance_from_callback.target());
EXPECT_EQ(0, instance_from_callback.srv_priority());
EXPECT_EQ(0, instance_from_callback.srv_weight());
EXPECT_EQ(fidl::To<std::vector<fuchsia::net::SocketAddress>>(kSocketAddresses),
instance_from_callback.addresses());
EXPECT_EQ(kText, instance_from_callback.text_strings());
}
// Tests the behavior of the resolver when configured to discover services on a local proxy host.
TEST_F(InstanceResolverTest, LocalProxyInstance) {
fuchsia::net::mdns::ServiceInstance instance_from_callback;
bool callback_called = false;
ServiceInstanceResolver under_test(
this, kServiceName, kInstanceName, now(), Media::kBoth, IpVersions::kBoth, kExcludeLocal,
kIncludeLocalProxies,
[&instance_from_callback, &callback_called](fuchsia::net::mdns::ServiceInstance instance) {
instance_from_callback = std::move(instance);
callback_called = true;
});
SetAgent(under_test);
under_test.Start(kLocalHostFullName);
// Expect a SRV and TXT questions on start.
ExpectQueryCall(DnsType::kSrv, MdnsNames::InstanceFullName(kInstanceName, kServiceName),
Media::kBoth, IpVersions::kBoth, now(), kAdditionalInterval,
kAdditionalIntervalMultiplier, kAdditionalMaxQueries, true);
ExpectQueryCall(DnsType::kTxt, MdnsNames::InstanceFullName(kInstanceName, kServiceName),
Media::kBoth, IpVersions::kBoth, now(), kAdditionalInterval,
kAdditionalIntervalMultiplier, kAdditionalMaxQueries, true);
ExpectPostTaskForTime(zx::sec(0), zx::sec(0));
ExpectNoOther();
under_test.OnAddLocalServiceInstance(
ServiceInstance(kServiceName, kInstanceName, kHostName, kSocketAddresses, kText),
kFromLocalProxyHost);
EXPECT_TRUE(callback_called);
EXPECT_EQ(kServiceName, instance_from_callback.service());
EXPECT_EQ(kInstanceName, instance_from_callback.instance());
EXPECT_EQ(kHostName, instance_from_callback.target());
EXPECT_EQ(0, instance_from_callback.srv_priority());
EXPECT_EQ(0, instance_from_callback.srv_weight());
EXPECT_EQ(fidl::To<std::vector<fuchsia::net::SocketAddress>>(kSocketAddresses),
instance_from_callback.addresses());
EXPECT_EQ(kText, instance_from_callback.text_strings());
}
// Regression test for b/274710801
TEST_F(InstanceResolverTest, LocalProxyInstanceFail) {
constexpr char kAnotherInstanceName[] = "another-testinstance";
fuchsia::net::mdns::ServiceInstance instance_from_callback;
bool callback_called = false;
ServiceInstanceResolver under_test(
this, kServiceName, kInstanceName, now(), Media::kBoth, IpVersions::kBoth, kIncludeLocal,
kIncludeLocalProxies,
[&instance_from_callback, &callback_called](fuchsia::net::mdns::ServiceInstance instance) {
instance_from_callback = std::move(instance);
callback_called = true;
});
SetAgent(under_test);
under_test.Start(kLocalHostFullName);
// Expect a SRV and TXT questions on start.
ExpectQueryCall(DnsType::kSrv, MdnsNames::InstanceFullName(kInstanceName, kServiceName),
Media::kBoth, IpVersions::kBoth, now(), kAdditionalInterval,
kAdditionalIntervalMultiplier, kAdditionalMaxQueries, true);
ExpectQueryCall(DnsType::kTxt, MdnsNames::InstanceFullName(kInstanceName, kServiceName),
Media::kBoth, IpVersions::kBoth, now(), kAdditionalInterval,
kAdditionalIntervalMultiplier, kAdditionalMaxQueries, true);
ExpectPostTaskForTime(zx::sec(0), zx::sec(0));
ExpectNoOther();
// |ServiceInstanceResolver| |under_test| test is created with |kInstanceName| while we are trying
// to add local service instance of name |kAnotherInstanceName|. We dont expect the callback to
// get triggered.
under_test.OnAddLocalServiceInstance(
ServiceInstance(kServiceName, kAnotherInstanceName, kHostName, kSocketAddresses, kText),
kFromLocalProxyHost);
EXPECT_FALSE(callback_called);
}
TEST_F(InstanceResolverTest, ResponseWithoutAaaa) {
fuchsia::net::mdns::ServiceInstance instance_from_callback;
bool callback_called = false;
ServiceInstanceResolver under_test(
this, kServiceName, kInstanceName, now(), Media::kBoth, IpVersions::kBoth, kExcludeLocal,
kIncludeLocalProxies,
[&instance_from_callback, &callback_called](fuchsia::net::mdns::ServiceInstance instance) {
instance_from_callback = std::move(instance);
callback_called = true;
});
SetAgent(under_test);
under_test.Start(kLocalHostFullName);
// Expect a SRV & TXT questions on start.
ExpectQueryCall(DnsType::kSrv, MdnsNames::InstanceFullName(kInstanceName, kServiceName),
Media::kBoth, IpVersions::kBoth, now(), kAdditionalInterval,
kAdditionalIntervalMultiplier, kAdditionalMaxQueries, true);
ExpectQueryCall(DnsType::kTxt, MdnsNames::InstanceFullName(kInstanceName, kServiceName),
Media::kBoth, IpVersions::kBoth, now(), kAdditionalInterval,
kAdditionalIntervalMultiplier, kAdditionalMaxQueries, true);
ExpectPostTaskForTime(zx::sec(0), zx::sec(0));
ExpectNoOther();
// Receive publication with no aaaa record.
ReplyAddress sender_address(inet::SocketAddress(0xfe80, 1, inet::IpPort::From_uint16_t(5353)),
inet::IpAddress(0xfe80, 100), 1, Media::kWireless, IpVersions::kV6);
ReceivePublication(under_test, kHostFullName, kServiceName, kInstanceName, kPort, kText,
sender_address, false);
// Expect a aaaa query call.
ExpectQueryCall(DnsType::kAaaa, kHostFullName, Media::kBoth, IpVersions::kBoth, now(),
kAdditionalInterval, kAdditionalIntervalMultiplier, kAdditionalMaxQueries, true);
// Receive a response with only an aaaa record.
ReceiveAddress(under_test, kHostFullName, sender_address);
ExpectPostTaskForTime(zx::sec(0), zx::sec(0));
ExpectNoOther();
}
} // namespace test
} // namespace mdns