blob: 1fc1513ec8cb9c2472d3a06f351ffdd2d4723e50 [file] [log] [blame]
// Copyright 2018 The Chromium 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 <signal.h>
#include <unistd.h>
#include <algorithm>
#include <map>
#include <memory>
#include <vector>
// TODO(rwkeane): Remove references to platform/impl
#include "osp/impl/discovery/mdns/mdns_responder_adapter_impl.h"
#include "platform/api/network_interface.h"
#include "platform/api/time.h"
#include "platform/base/error.h"
#include "platform/impl/logging.h"
#include "platform/impl/platform_client_posix.h"
#include "platform/impl/task_runner.h"
#include "platform/impl/udp_socket_reader_posix.h"
// This file contains a demo of our mDNSResponder wrapper code. It can both
// listen for mDNS services and advertise an mDNS service. The command-line
// usage is:
// mdns_demo [service_type] [service_instance_name]
// service_type defaults to '_openscreen._udp' and service_instance_name
// defaults to ''. service_type determines services the program listens for and
// when service_instance_name is not empty, a service of
// 'service_instance_name.service_type' is also advertised.
//
// The program will print a list of discovered services when it receives a USR1
// or INT signal. The pid is printed at the beginning of the program to
// facilitate this.
//
// There are a few known bugs around the handling of record events, so this
// shouldn't be expected to be a source of truth, nor should it be expected to
// be correct after running for a long time.
namespace openscreen {
namespace osp {
namespace {
bool g_done = false;
bool g_dump_services = false;
struct Service {
explicit Service(DomainName service_instance)
: service_instance(std::move(service_instance)) {}
~Service() = default;
DomainName service_instance;
DomainName domain_name;
IPAddress address;
uint16_t port;
std::vector<std::string> txt;
};
class DemoSocketClient : public UdpSocket::Client {
public:
explicit DemoSocketClient(MdnsResponderAdapterImpl* mdns) : mdns_(mdns) {}
void OnError(UdpSocket* socket, Error error) override {
// TODO(crbug.com/openscreen/66): Change to OSP_LOG_FATAL.
OSP_LOG_ERROR << "configuration failed for interface " << error.message();
OSP_CHECK(false);
}
void OnSendError(UdpSocket* socket, Error error) override {
OSP_UNIMPLEMENTED();
}
void OnRead(UdpSocket* socket, ErrorOr<UdpPacket> packet) override {
mdns_->OnRead(socket, std::move(packet));
}
private:
MdnsResponderAdapterImpl* mdns_;
};
using ServiceMap = std::map<DomainName, Service, DomainNameComparator>;
ServiceMap* g_services = nullptr;
void sigusr1_dump_services(int) {
g_dump_services = true;
}
void sigint_stop(int) {
OSP_LOG_INFO << "caught SIGINT, exiting...";
g_done = true;
}
std::vector<std::string> SplitByDot(const std::string& domain_part) {
std::vector<std::string> result;
auto copy_it = domain_part.begin();
for (auto it = domain_part.begin(); it != domain_part.end(); ++it) {
if (*it == '.') {
result.emplace_back(copy_it, it);
copy_it = it + 1;
}
}
if (copy_it != domain_part.end())
result.emplace_back(copy_it, domain_part.end());
return result;
}
void SignalThings() {
struct sigaction usr1_sa;
struct sigaction int_sa;
struct sigaction unused;
usr1_sa.sa_handler = &sigusr1_dump_services;
sigemptyset(&usr1_sa.sa_mask);
usr1_sa.sa_flags = 0;
int_sa.sa_handler = &sigint_stop;
sigemptyset(&int_sa.sa_mask);
int_sa.sa_flags = 0;
sigaction(SIGUSR1, &usr1_sa, &unused);
sigaction(SIGINT, &int_sa, &unused);
OSP_LOG_INFO << "signal handlers setup" << std::endl << "pid: " << getpid();
}
std::vector<std::unique_ptr<UdpSocket>> SetUpMulticastSockets(
TaskRunner* task_runner,
const std::vector<NetworkInterfaceIndex>& index_list,
UdpSocket::Client* client) {
std::vector<std::unique_ptr<UdpSocket>> sockets;
for (const auto ifindex : index_list) {
auto create_result =
UdpSocket::Create(task_runner, client, IPEndpoint{{}, 5353});
if (!create_result) {
OSP_LOG_ERROR << "failed to create IPv4 socket for interface " << ifindex
<< ": " << create_result.error().message();
continue;
}
std::unique_ptr<UdpSocket> socket = std::move(create_result.value());
socket->JoinMulticastGroup(IPAddress{224, 0, 0, 251}, ifindex);
socket->SetMulticastOutboundInterface(ifindex);
socket->Bind();
OSP_LOG_INFO << "listening on interface " << ifindex;
sockets.emplace_back(std::move(socket));
}
return sockets;
}
void LogService(const Service& s) {
OSP_LOG_INFO << "PTR: (" << s.service_instance << ")" << std::endl
<< "SRV: " << s.domain_name << ":" << s.port << std::endl
<< "TXT:";
for (const auto& l : s.txt) {
OSP_LOG_INFO << " | " << l;
}
OSP_LOG_INFO << "A: " << s.address;
}
void HandleEvents(MdnsResponderAdapterImpl* mdns_adapter) {
for (auto& ptr_event : mdns_adapter->TakePtrResponses()) {
auto it = g_services->find(ptr_event.service_instance);
switch (ptr_event.header.response_type) {
case QueryEventHeader::Type::kAdded:
case QueryEventHeader::Type::kAddedNoCache:
mdns_adapter->StartSrvQuery(ptr_event.header.socket,
ptr_event.service_instance);
mdns_adapter->StartTxtQuery(ptr_event.header.socket,
ptr_event.service_instance);
if (it == g_services->end()) {
g_services->emplace(ptr_event.service_instance,
Service(ptr_event.service_instance));
}
break;
case QueryEventHeader::Type::kRemoved:
// PTR may be removed and added without updating related entries (SRV
// and friends) so this simple logic is actually broken, but I don't
// want to do a better design or pointer hell for just a demo.
OSP_LOG_WARN << "ptr-remove: " << ptr_event.service_instance;
if (it != g_services->end())
g_services->erase(it);
break;
}
}
for (auto& srv_event : mdns_adapter->TakeSrvResponses()) {
auto it = g_services->find(srv_event.service_instance);
if (it == g_services->end())
continue;
switch (srv_event.header.response_type) {
case QueryEventHeader::Type::kAdded:
case QueryEventHeader::Type::kAddedNoCache:
mdns_adapter->StartAQuery(srv_event.header.socket,
srv_event.domain_name);
it->second.domain_name = std::move(srv_event.domain_name);
it->second.port = srv_event.port;
break;
case QueryEventHeader::Type::kRemoved:
OSP_LOG_WARN << "srv-remove: " << srv_event.service_instance;
it->second.domain_name = DomainName();
it->second.port = 0;
break;
}
}
for (auto& txt_event : mdns_adapter->TakeTxtResponses()) {
auto it = g_services->find(txt_event.service_instance);
if (it == g_services->end())
continue;
switch (txt_event.header.response_type) {
case QueryEventHeader::Type::kAdded:
case QueryEventHeader::Type::kAddedNoCache:
it->second.txt = std::move(txt_event.txt_info);
break;
case QueryEventHeader::Type::kRemoved:
OSP_LOG_WARN << "txt-remove: " << txt_event.service_instance;
it->second.txt.clear();
break;
}
}
for (const auto& a_event : mdns_adapter->TakeAResponses()) {
// TODO(btolsch): If multiple SRV records specify the same domain, the A
// will only update the first. I didn't think this would happen but I
// noticed this happens for cast groups.
auto it = std::find_if(g_services->begin(), g_services->end(),
[&a_event](const std::pair<DomainName, Service>& s) {
return s.second.domain_name == a_event.domain_name;
});
if (it == g_services->end())
continue;
switch (a_event.header.response_type) {
case QueryEventHeader::Type::kAdded:
case QueryEventHeader::Type::kAddedNoCache:
it->second.address = a_event.address;
break;
case QueryEventHeader::Type::kRemoved:
OSP_LOG_WARN << "a-remove: " << a_event.domain_name;
it->second.address = IPAddress(0, 0, 0, 0);
break;
}
}
}
void BrowseDemo(TaskRunner* task_runner,
const std::string& service_name,
const std::string& service_protocol,
const std::string& service_instance) {
SignalThings();
std::vector<std::string> labels{service_name, service_protocol};
ErrorOr<DomainName> service_type =
DomainName::FromLabels(labels.begin(), labels.end());
if (!service_type) {
OSP_LOG_ERROR << "bad domain labels: " << service_name << ", "
<< service_protocol;
return;
}
auto mdns_adapter = std::make_unique<MdnsResponderAdapterImpl>();
mdns_adapter->Init();
mdns_adapter->SetHostLabel("gigliorononomicon");
const std::vector<InterfaceInfo> interfaces = GetNetworkInterfaces();
std::vector<NetworkInterfaceIndex> index_list;
for (const auto& interface : interfaces) {
OSP_LOG_INFO << "Found interface: " << interface;
if (!interface.addresses.empty()) {
index_list.push_back(interface.index);
}
}
OSP_LOG_IF(WARN, index_list.empty())
<< "No network interfaces had usable addresses for mDNS.";
DemoSocketClient client(mdns_adapter.get());
auto sockets = SetUpMulticastSockets(task_runner, index_list, &client);
// The code below assumes the elements in |sockets| is in exact 1:1
// correspondence with the elements in |index_list|. Crash the demo if any
// sockets are missing (i.e., failed to be set up).
OSP_CHECK_EQ(sockets.size(), index_list.size());
// Listen on all interfaces.
auto socket_it = sockets.begin();
for (NetworkInterfaceIndex index : index_list) {
const auto& interface =
*std::find_if(interfaces.begin(), interfaces.end(),
[index](const openscreen::InterfaceInfo& info) {
return info.index == index;
});
// Pick any address for the given interface.
mdns_adapter->RegisterInterface(interface, interface.addresses.front(),
socket_it->get());
++socket_it;
}
if (!service_instance.empty()) {
mdns_adapter->RegisterService(service_instance, service_name,
service_protocol, DomainName(), 12345,
{{"k1", "yurtle"}, {"k2", "turtle"}});
}
for (const std::unique_ptr<UdpSocket>& socket : sockets) {
mdns_adapter->StartPtrQuery(socket.get(), service_type.value());
}
while (!g_done) {
HandleEvents(mdns_adapter.get());
if (g_dump_services) {
OSP_LOG_INFO << "num services: " << g_services->size();
for (const auto& s : *g_services) {
LogService(s.second);
}
if (!service_instance.empty()) {
mdns_adapter->UpdateTxtData(
service_instance, service_name, service_protocol,
{{"k1", "oogley"}, {"k2", "moogley"}, {"k3", "googley"}});
}
g_dump_services = false;
}
mdns_adapter->RunTasks();
}
OSP_LOG_INFO << "num services: " << g_services->size();
for (const auto& s : *g_services) {
LogService(s.second);
}
for (const std::unique_ptr<UdpSocket>& socket : sockets) {
mdns_adapter->DeregisterInterface(socket.get());
}
mdns_adapter->Close();
}
} // namespace
} // namespace osp
} // namespace openscreen
int main(int argc, char** argv) {
using openscreen::Clock;
using openscreen::PlatformClientPosix;
openscreen::SetLogLevel(openscreen::LogLevel::kVerbose);
std::string service_instance;
std::string service_type("_openscreen._udp");
if (argc >= 2)
service_type = argv[1];
if (argc >= 3)
service_instance = argv[2];
if (service_type.size() && service_type[0] == '.')
return 1;
auto labels = openscreen::osp::SplitByDot(service_type);
if (labels.size() != 2)
return 1;
openscreen::osp::ServiceMap services;
openscreen::osp::g_services = &services;
PlatformClientPosix::Create(std::chrono::milliseconds(50));
openscreen::osp::BrowseDemo(
PlatformClientPosix::GetInstance()->GetTaskRunner(), labels[0], labels[1],
service_instance);
PlatformClientPosix::ShutDown();
openscreen::osp::g_services = nullptr;
return 0;
}