DNSServiceDiscoveryUnicast/dns-sd.c (163 lines of code) (raw):
/* Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the MIT License. */
#include "dns-sd.h"
#include <applibs/log.h>
#include <errno.h>
#include <resolv.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#define DNS_SERVER_PORT 53
#define QUERY_BUF_SIZE 2048u
#define DISPLAY_BUF_SIZE 256u
int SendDnsQuery(const char *dName, int class, int type, char* answerBuf, int answerBufSize)
{
static char queryBuf[QUERY_BUF_SIZE];
if (!dName) {
Log_Debug("ERROR: Can't send DNS query as the domain name is null.\n");
errno = EINVAL;
return -1;
}
// Construct the DNS query to send
Log_Debug("INFO: Sending DNS query to resolve domain name [%s]...\n", dName);
int ret = res_init();
if (ret) {
Log_Debug("ERROR: res_init: %d (%s)\n", errno, strerror(errno));
return -1;
}
int messageSize =
res_mkquery(ns_o_query, dName, class, type, NULL, 0, NULL, queryBuf, QUERY_BUF_SIZE);
if (messageSize <= 0) {
Log_Debug("ERROR: res_mkquery: %d (%s)\n", errno, strerror(errno));
return -1;
}
int answer = res_send(queryBuf, messageSize, answerBuf, answerBufSize);
if (answer < 0) {
Log_Debug("ERROR: res_send: %d (%s)\n", errno, strerror(errno));
return -1;
}
return answer;
}
int SendServiceDiscoveryQuery(const char *dName, char* answerBuf, int answerBufSize)
{
return SendDnsQuery(dName, ns_c_in, ns_t_ptr, answerBuf, answerBufSize);
}
int SendServiceInstanceDetailsQuery(const char *instanceName, char* answerBuf, int answerBufSize)
{
return SendDnsQuery(instanceName, ns_c_in, ns_t_any, answerBuf, answerBufSize);
}
int ProcessMessageBySection(char *buf, ssize_t len, ns_msg msg, ns_sect section,
ServiceInstanceDetails **instanceDetails)
{
static char displayBuf[DISPLAY_BUF_SIZE];
ns_rr rr;
int messageCount = ns_msg_count(msg, section);
if (!(*instanceDetails)) {
// Allocate for ServiceInstanceDetails and initialize its members.
*instanceDetails = malloc(sizeof(ServiceInstanceDetails));
if (!(*instanceDetails)) {
Log_Debug("ERROR: recvfrom: %d (%s)\n", errno, strerror(errno));
return -1;
}
(*instanceDetails)->name = NULL;
(*instanceDetails)->host = NULL;
(*instanceDetails)->ipv4Address.s_addr = INADDR_NONE;
(*instanceDetails)->port = 0;
(*instanceDetails)->txtData = NULL;
(*instanceDetails)->txtDataLength = 0;
}
// Parse each message
for (int i = 0; i < messageCount; ++i) {
if (ns_parserr(&msg, section, i, &rr)) {
Log_Debug("ERROR: ns_parserr: %d (%s)\n", errno, strerror(errno));
return -1;
}
switch (ns_rr_type(rr)) {
case ns_t_ptr: {
int compressedNameLength =
dn_expand(buf, buf + len, ns_rr_rdata(rr), displayBuf, sizeof(displayBuf));
if (compressedNameLength > 0 && !(*instanceDetails)->name) {
(*instanceDetails)->name = strdup(displayBuf);
if (!(*instanceDetails)->name) {
Log_Debug("ERROR: strdup for instance name failed: %d (%s)\n", errno,
strerror(errno));
return -1;
}
}
break;
}
case ns_t_srv: {
// Parse the SRV record and populate the port and host fields in instance details as per
// DNS SRV record specification: https://tools.ietf.org/rfc/rfc2782.txt
// SRV record format: Priority| Weight | Port | Target
// (2 Bytes)|(2 Bytes)|(2 Bytes)|(Remaining Bytes)
const char *data = ns_rr_rdata(rr);
int compressedTargetDomainNameLength = dn_expand(
buf, buf + len, data + sizeof(uint16_t) + sizeof(uint16_t) + sizeof(uint16_t),
displayBuf, sizeof(displayBuf));
if ((*instanceDetails)->port == 0 && compressedTargetDomainNameLength > 0 &&
!(*instanceDetails)->host) {
(*instanceDetails)->port =
(uint16_t)ns_get16(data + sizeof(uint16_t) + sizeof(uint16_t));
(*instanceDetails)->host = strdup(displayBuf);
if (!(*instanceDetails)->host) {
Log_Debug("ERROR: strdup: %d (%s)\n", errno, strerror(errno));
return -1;
}
Log_Debug("%s\n", (*instanceDetails)->host);
}
break;
}
case ns_t_txt: {
// Populate name field in instance details if it hasn't been set
if (!(*instanceDetails)->name) {
(*instanceDetails)->name = strdup(ns_rr_name(rr));
if (!(*instanceDetails)->name) {
Log_Debug("ERROR: strdup(ns_rr_name(rr)): %d (%s)\n", errno, strerror(errno));
return -1;
}
}
// Get TXT record, populate txtData and txtDataLength fields in instance details
if (!(*instanceDetails)->txtData) {
(*instanceDetails)->txtData =
malloc(sizeof(unsigned char) * (size_t)(ns_rr_rdlen(rr)));
if (!(*instanceDetails)->txtData) {
Log_Debug("ERROR: malloc: %d (%s)\n", errno, strerror(errno));
return -1;
}
memcpy((*instanceDetails)->txtData, ns_rr_rdata(rr), ns_rr_rdlen(rr));
(*instanceDetails)->txtDataLength = ns_rr_rdlen(rr);
}
break;
}
case ns_t_a: {
// Get A record (host address), populate ipv4Address field in instance details
if (ns_rr_rdlen(rr) == sizeof((*instanceDetails)->ipv4Address)) {
memcpy(&(*instanceDetails)->ipv4Address.s_addr, ns_rr_rdata(rr), ns_rr_rdlen(rr));
} else {
Log_Debug("ERROR: Invalid DNS A record length: %d\n", ns_rr_rdlen(rr));
}
break;
}
default:
break;
}
}
return 0;
}
int ProcessDnsResponse(ServiceInstanceDetails **instanceDetails, char* answerBuf, int len)
{
ns_msg msg;
// Decode received response
if (ns_initparse(answerBuf, len, &msg) != 0) {
Log_Debug("ERROR: ns_initparse: %d (%s)\n", errno, strerror(errno));
goto fail;
}
if (ProcessMessageBySection(answerBuf, len, msg, ns_s_an, instanceDetails) != 0) {
goto fail;
}
if (ProcessMessageBySection(answerBuf, len, msg, ns_s_ar, instanceDetails) != 0) {
goto fail;
}
return 0;
fail:
FreeServiceInstanceDetails(*instanceDetails);
*instanceDetails = NULL;
return -1;
}
void FreeServiceInstanceDetails(const ServiceInstanceDetails *details)
{
if (details) {
free(details->name);
free(details->host);
free(details->txtData);
free((void *)details);
}
}