zeroconf/mdnsd.c

2539 lines
64 KiB
C

/*
* tinysvcmdns - a tiny MDNS implementation for publishing services
* Copyright (C) 2011 Darell Tan
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/* mdns core and API */
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#define LOG_ERR 3
#else
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <syslog.h>
#endif
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <assert.h>
#include <pthread.h>
#include <sys/time.h>
//#include <tinyara/clock.h>
#ifndef NSEC_PER_SEC
#define NSEC_PER_SEC (1000000000L)
#endif
#include <semaphore.h>
#include <errno.h>
#define get_errno() (errno)
/*
* Define a proper IP socket level if not already done.
* Required to compile on OS X
*/
#ifndef SOL_IP
#define SOL_IP IPPROTO_IP
#endif
#include "mdns.h"
#include "mdnsd.h"
#if (MDNS_DEBUG_PRINTF == 1) && (MDNS_RR_DEBUG == 1)
#define MDNSD_RR_DEBUG
#endif
#if (MDNS_DEBUG_PRINTF == 1) && (MDNS_MEMORY_DEBUG == 1)
#define MDNSD_MEMORY_DEBUG
#endif
#define THREAD_MAINLOOP_NAME "MDNS"
#define THREAD_MAINLOOP_STACKSIZE 4096
#define PIPE_SOCKET_TYPE 0
#define PIPE_SOCKET_PORT0 65353
#define PIPE_SOCKET_PORT1 65354
#define MDNS_ADDR "224.0.0.251"
#define MDNS_PORT 5353
#define MDNS_SUFFIX_LOCAL ".local"
#define MDNS_SUFFIX_SITE ".site"
#define MDNS_CHECK_SUBTYPE_STR "._sub."
#define PACKET_SIZE 1536 /* maximum packet size : */
//#define PACKET_SIZE 65536
#define SERVICES_DNS_SD_NLABEL \
((uint8_t *) "\x09_services\x07_dns-sd\x04_udp\x05local")
#define TIME_GET(time) gettimeofday(&time, NULL)
#define TIME_DIFF_USEC(old_time, cur_time) \
((cur_time.tv_sec * 1000000 + cur_time.tv_usec) - (old_time.tv_sec * 1000000 + old_time.tv_usec))
#define MAX_ECONNRESET_COUNT 5
enum mdns_cache_status {
CACHE_SLEEP = 0,
CACHE_NORMAL = 1,
CACHE_RESOLVE_HOSTNAME = 2,
CACHE_SERVICE_DISCOVERY = 3,
};
enum mdns_domain {
MDNS_DOMAIN_UNKNOWN = 0,
MDNS_DOMAIN_LOCAL = 1,
#if defined(CONFIG_NETUTILS_MDNS_XMDNS)
MDNS_DOMAIN_SITE = 2,
#endif
};
struct mdnsd {
pthread_mutex_t data_lock;
sem_t sendmsg_sem;
int sockfd;
int notify_pipe[2];
int stop_flag;
int dump_cache;
int sendmsg_requested;
int domain;
enum mdns_cache_status c_status;
char *c_filter;
struct rr_group *cache;
struct rr_list *query;
#ifndef MDNS_NO_RESPONDER_SUPPORT
struct rr_group *group;
struct rr_list *announce;
struct rr_list *services;
struct rr_list *probe;
uint8_t *hostname; /* hostname can be changed if name collision occur */
uint8_t *hostname_org;
#endif
};
#ifndef MDNS_NO_RESPONDER_SUPPORT
struct mdns_service {
struct rr_list *entries;
};
#endif
static void update_cache(struct mdnsd *svr);
static void clear_service_discovery_result(struct mdns_service_info
*service_list, int num_of_services);
static void release_mdns_context_resource(void);
static struct mdnsd *g_svr = NULL;
static struct mdns_service_info *g_service_list = NULL;
static pthread_mutex_t g_cmd_lock;
static int g_cmd_lock_initialized = 0;
/////////////////////////////////
static void log_message(int loglevel, char *fmt_str, ...) {
va_list ap;
char buf[2048];
va_start(ap, fmt_str);
vsnprintf(buf, 2047, fmt_str, ap);
va_end(ap);
buf[2047] = 0;
fprintf(stderr, "%s\n", buf);
}
//#ifdef MDNSD_RR_DEBUG
static void print_rr_entry(struct rr_entry *rr_e)
{
char *str1, *str2;
if (!rr_e) {
MDNS_RR_DEBUG_PRINTF("ERROR: No RR Entry\n");
return;
}
if (rr_e->name) {
str1 = nlabel_to_str(rr_e->name);
} else {
str1 = NULL;
}
if ((rr_e->type == RR_PTR) && rr_e->data.PTR.name) {
str2 = nlabel_to_str(rr_e->data.PTR.name);
} else if ((rr_e->type == RR_SRV) && rr_e->data.SRV.target) {
str2 = nlabel_to_str(rr_e->data.SRV.target);
} else {
str2 = NULL;
}
MDNS_RR_DEBUG_PRINTF("type:%s, ttl=%d, time=%d, ca_fl=%d, rr_class=%d, name=[%s]", rr_get_type_name(rr_e->type), rr_e->ttl, (unsigned int)(time(NULL) - rr_e->update_time), (int)rr_e->cache_flush, rr_e->rr_class, str1 ? str1 : "NULL");
if (rr_e->type == RR_SRV || rr_e->type == RR_PTR) {
MDNS_RR_DEBUG_PRINTF(", target=[%s]\n", str2 ? str2 : "NULL");
} else {
MDNS_RR_DEBUG_PRINTF("\n");
}
if (str1) {
MDNS_FREE(str1);
}
if (str2) {
MDNS_FREE(str2);
}
}
static void print_cache(struct mdnsd *svr)
{
MDNS_RR_DEBUG_PRINTF("\n");
MDNS_RR_DEBUG_PRINTF(" Multicast DNS Cache\n");
for (struct rr_group *group = svr->cache; group; group = group->next) {
char *pname = group->name? nlabel_to_str(group->name): NULL;
MDNS_RR_DEBUG_PRINTF(
"==================================================\n"
" Group: %s\n"
"==================================================\n",
pname ? pname : "Unknown");
if (pname) {
MDNS_FREE(pname);
}
for (struct rr_list *list = group->rr; list; list = list->next) {
struct rr_entry *entry = list->e;
if (entry) {
print_rr_entry(entry);
}
}
}
MDNS_RR_DEBUG_PRINTF("==================================================\n\n");
}
//#endif /* MDNSD_RR_DEBUG */
static int check_mdns_domain(const char *name)
{
char *str = NULL;
int domain = MDNS_DOMAIN_UNKNOWN;
if (name == NULL) {
goto done;
}
#if defined(CONFIG_NETUTILS_MDNS_XMDNS)
str = strstr(name, MDNS_SUFFIX_SITE);
if (str && strcmp(str, MDNS_SUFFIX_SITE) == 0) {
domain = MDNS_DOMAIN_SITE;
goto done;
}
#endif
str = strstr(name, MDNS_SUFFIX_LOCAL);
if (str && strcmp(str, MDNS_SUFFIX_LOCAL) == 0) {
domain = MDNS_DOMAIN_LOCAL;
goto done;
}
domain = MDNS_DOMAIN_UNKNOWN;
done:
return domain;
}
static char *get_service_type_without_subtype(char *name)
{
char *str = strstr(name, MDNS_CHECK_SUBTYPE_STR);
if (str) {
str += strlen(MDNS_CHECK_SUBTYPE_STR);
} else {
str = name;
}
return str;
}
#ifndef MDNS__NO_RESPONDER_SUPPORT
static int lookup_hostname(struct mdnsd *svr, char *hostname)
{
int result = -1;
int b_found = 0;
pthread_mutex_lock(&svr->data_lock);
for (struct rr_group *group = svr->cache; group; group = group->next) {
for (struct rr_list *list = group->rr; list; list = list->next) {
struct rr_entry *entry = list->e;
int ret;
if (entry && entry->name) {
char *e_name = nlabel_to_str(entry->name);
ret = strncmp(e_name, hostname, strlen(hostname));
MDNS_FREE(e_name);
} else {
ret = -1;
}
if (ret == 0) {
b_found = 1;
break;
}
}
if (b_found) {
break;
}
}
pthread_mutex_unlock(&svr->data_lock);
if (b_found) {
result = 0;
}
return result;
}
#endif /* !defined MDNS_NO_RESPONDER_SUPPORT */
static int lookup_hostname_to_addr(struct mdnsd *svr, char *hostname, uint32_t *ipaddr)
{
int result = -1;
int b_found = 0;
update_cache(svr);
pthread_mutex_lock(&svr->data_lock);
for (struct rr_group *group = svr->cache; group; group = group->next) {
for (struct rr_list *list = group->rr; list; list = list->next) {
struct rr_entry *entry = list->e;
if (entry && (entry->type == RR_A)) { // currently, support only ipv4
int ret;
if (entry->name) {
char *e_name;
e_name = nlabel_to_str(entry->name);
ret = strncmp(e_name, hostname, strlen(hostname));
MDNS_FREE(e_name);
} else {
ret = -1;
}
if (ret == 0) {
*ipaddr = entry->data.A.addr;
b_found = 1;
break;
}
}
}
if (b_found) {
break;
}
}
pthread_mutex_unlock(&svr->data_lock);
if (b_found) {
result = 0;
}
return result;
}
static int lookup_service(struct mdnsd *svr, char *type, struct mdns_service_info *service_list, int *num_of_services)
{
int result = -1;
int result_cnt = 0;
uint8_t *type_nlabel = create_nlabel(type);
char *type_without_subtype = get_service_type_without_subtype(type);
clear_service_discovery_result(service_list, MAX_NUMBER_OF_SERVICE_DISCOVERY_RESULT);
update_cache(svr);
pthread_mutex_lock(&svr->data_lock);
struct rr_group *ptr_grp = rr_group_find(svr->cache, (uint8_t *)type_nlabel);
MDNS_FREE(type_nlabel);
if (ptr_grp) {
for (struct rr_list *list = ptr_grp->rr; list; list = list->next) {
struct rr_entry *entry = list->e;
if (entry && (entry->type == RR_PTR)) {
if (entry->data.PTR.name) { /* SRV's name */
struct rr_group *srv_grp = rr_group_find(svr->cache,
(uint8_t *)entry->data.PTR.name);
if (srv_grp) {
/* find service */
struct rr_entry *srv_e = rr_entry_find(srv_grp->rr, entry->data.PTR.name,
RR_SRV);
if (srv_e && srv_e->name) {
char *name = nlabel_to_str(srv_e->name); /* full service name */
char *ptr = strstr(name,
type_without_subtype); /* separate instance name and service type */
if (ptr && (ptr > name)) {
*(ptr - 1) = '\0';
} else {
MDNS_FREE(name);
continue;
}
/* set instance name */
service_list[result_cnt].instance_name = MDNS_STRDUP(name);
/* set service type */
service_list[result_cnt].type = MDNS_STRDUP(ptr);
MDNS_FREE(name);
/* set hostname */
if (srv_e->data.SRV.target) {
struct rr_group *a_grp = NULL;
name = nlabel_to_str(srv_e->data.SRV.target);
name[strlen(name) - 1] = '\0';
service_list[result_cnt].hostname = MDNS_STRDUP(name);
MDNS_FREE(name);
/* ip address */
a_grp = rr_group_find(svr->cache, (uint8_t *)srv_e->data.SRV.target);
if (a_grp) {
struct rr_entry *a_e = rr_entry_find(a_grp->rr, srv_e->data.SRV.target, RR_A);
if (a_e) {
service_list[result_cnt].ipaddr = a_e->data.A.addr;
}
}
}
/* port */
service_list[result_cnt].port = srv_e->data.SRV.port;
result_cnt++; /* increase result count */
if (result_cnt >= MAX_NUMBER_OF_SERVICE_DISCOVERY_RESULT) {
break;
}
}
}
}
}
}
}
pthread_mutex_unlock(&svr->data_lock);
*num_of_services = result_cnt;
if (*num_of_services > 0) {
result = 0;
}
return result;
}
static int create_recv_sock(int domain)
{
char *addr;
int port;
int r;
switch (domain) {
#if defined(CONFIG_NETUTILS_MDNS_XMDNS)
case MDNS_DOMAIN_SITE:
addr = CONFIG_NETUTILS_MDNS_XMDNS_MULTICAST_ADDR;
port = CONFIG_NETUTILS_MDNS_XMDNS_PORT_NUM;
break;
#endif
case MDNS_DOMAIN_LOCAL:
default:
addr = MDNS_ADDR;
port = MDNS_PORT;
break;
}
int sd = socket(AF_INET, SOCK_DGRAM, 0);
if (sd < 0) {
log_message(LOG_ERR, "recv socket(): %s\n", strerror(errno));
return sd;
}
int on = 1;
if ((r = setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))) < 0) {
close(sd);
log_message(LOG_ERR, "recv setsockopt(SO_REUSEADDR): %s\n", strerror(errno));
return r;
}
#if !defined(WIN32) && defined(SO_REUSEPORT)
if (!getsockopt(sd, SOL_SOCKET, SO_REUSEPORT, &on, &addrlen)) {
if ((r = setsockopt(sd, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on))) < 0) {
log_message(LOG_ERR, "recv setsockopt(SO_REUSEPORT): %d; %s\n", r, strerror(errno));
}
}
#endif
// (issue #5 Tinysvcmdns invisible from chrome app mdns-browser)
// TTL -> 255 in case receivers demand it (unrouted pkt)
int ttl = 255;
if ((r = setsockopt(sd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl))) < 0) {
log_message(LOG_ERR, "recv setsockopt(IP_MULTICAST_IP): %s", strerror(errno));
return r;
}
/* bind to an address */
struct sockaddr_in serveraddr = {0};
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(port);
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); /* receive multicast */
if ((r = bind(sd, (struct sockaddr *)&serveraddr, sizeof(serveraddr))) < 0) {
log_message(LOG_ERR, "recv bind(): %s\n", strerror(errno));
}
// add membership to receiving socket
DECL_ZERO_STRUCT(mreq, ip_mreq);
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
mreq.imr_multiaddr.s_addr = inet_addr(addr);
if ((r = setsockopt(sd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq))) < 0) {
close(sd);
log_message(LOG_ERR, "recv setsockopt(IP_ADD_MEMBERSHIP): %s\n", strerror(errno));
return r;
}
#ifdef MDNS_ENABLE_LOOPBACK
// enable loopback in case someone else needs the data
int flag = 1;
#else
// disable loopback
int flag = 0;
#endif
if ((r = setsockopt(sd, IPPROTO_IP, IP_MULTICAST_LOOP, &flag, sizeof(flag))) < 0) {
close(sd);
log_message(LOG_ERR, "recv setsockopt(IP_MULTICAST_LOOP): %s\n", strerror(errno));
return r;
}
#ifdef IP_PKTINFO
if ((r = setsockopt(sd, SOL_IP, IP_PKTINFO, &on, sizeof(on))) < 0) {
close(sd);
log_message(LOG_ERR, "recv setsockopt(IP_PKTINFO): %s\n", strerror(errno));
return r;
}
#endif
return sd;
}
static ssize_t send_packet_to(int fd, const void *data, size_t len, struct sockaddr_in *toaddr) {
return sendto(fd, data, len, 0, (struct sockaddr *) toaddr, sizeof(struct sockaddr_in));
}
// populate the specified list which matches the RR name and type
static int populate_query(struct mdnsd *svr, struct rr_list **rr_head)
{
int num_qns = 0;
// check if we have the records
pthread_mutex_lock(&svr->data_lock);
while (svr->query) {
struct rr_entry *qn_e = rr_list_remove(&svr->query, svr->query->e);
if (qn_e == NULL) {
break;
}
num_qns += rr_list_append(rr_head, qn_e);
}
pthread_mutex_unlock(&svr->data_lock);
return num_qns;
}
static ssize_t send_packet(int fd, const void *data, size_t len, int domain) {
static struct sockaddr_in toaddr;
char *addr;
int port;
switch (domain) {
#if defined(CONFIG_NETUTILS_MDNS_XMDNS)
case MDNS_DOMAIN_SITE:
addr = CONFIG_NETUTILS_MDNS_XMDNS_MULTICAST_ADDR;
port = CONFIG_NETUTILS_MDNS_XMDNS_PORT_NUM;
break;
#endif
case MDNS_DOMAIN_LOCAL:
default:
addr = MDNS_ADDR;
port = MDNS_PORT;
break;
}
memset(&toaddr, 0, sizeof(struct sockaddr_in));
toaddr.sin_family = AF_INET;
toaddr.sin_port = htons(port);
toaddr.sin_addr.s_addr = inet_addr(addr);
return send_packet_to( fd, data, len, &toaddr);
}
#ifndef MDNS_NO_RESPONDER_SUPPORT
// populate the specified list which matches the RR name and type
static int populate_probe(struct mdnsd *svr, struct rr_list **rr_head)
{
int num_qns = 0;
// check if we have the records
pthread_mutex_lock(&svr->data_lock);
while (svr->probe) {
struct rr_entry *qn_e = rr_list_remove(&svr->probe, svr->probe->e);
if (qn_e == NULL) {
break;
}
num_qns += rr_list_append(rr_head, qn_e);
}
pthread_mutex_unlock(&svr->data_lock);
return num_qns;
}
// populate the specified list which matches the RR name and type
// type can be RR_ANY, which populates all entries EXCEPT RR_NSEC
static int populate_answers(struct mdnsd *svr, struct rr_list **rr_head, uint8_t *name, enum rr_type type) {
int num_ans = 0;
pthread_mutex_lock(&svr->data_lock);
if (type == RR_PTR && 0 == cmp_nlabel(SERVICES_DNS_SD_NLABEL, name)) {
// add answers for all services
for (struct rr_list *n = svr->services; n; n = n->next) {
// if (s->service->service && s->service->proto)
num_ans += rr_list_append(rr_head, n->e);
}
} else {
// check if we have the records
struct rr_group *ans_grp = rr_group_find(svr->group, name);
if (ans_grp != NULL) {
// decide which records should go into answers
for (struct rr_list *n = ans_grp->rr; n; n = n->next) {
// exclude NSEC for RR_ANY
if (type == RR_ANY && n->e->type == RR_NSEC)
continue;
if ((type == n->e->type || type == RR_ANY) && cmp_nlabel(name, n->e->name) == 0) {
num_ans += rr_list_append(rr_head, n->e);
}
}
}
}
pthread_mutex_unlock(&svr->data_lock);
return num_ans;
}
// given a list of RRs, look up related records and add them
static void add_related_rr(struct mdnsd *svr, struct rr_list *list, struct mdns_pkt *reply) {
for (; list; list = list->next) {
struct rr_entry *ans = list->e;
switch (ans->type) {
case RR_PTR:
// target host A, AAAA records
reply->num_add_rr += populate_answers(svr, &reply->rr_add,
MDNS_RR_GET_PTR_NAME(ans), RR_ANY);
break;
case RR_SRV:
// target host A, AAAA records
reply->num_add_rr += populate_answers(svr, &reply->rr_add,
ans->data.SRV.target, RR_ANY);
// perhaps TXT records of the same name?
// if we use RR_ANY, we risk pulling in the same RR_SRV
reply->num_add_rr += populate_answers(svr, &reply->rr_add,
ans->name, RR_TXT);
break;
case RR_A:
case RR_AAAA:
reply->num_add_rr += populate_answers(svr, &reply->rr_add,
ans->name, RR_NSEC);
break;
default:
// nothing to add
break;
}
}
}
// creates an announce packet given the type name PTR
static void announce_srv(struct mdnsd *svr, struct mdns_pkt *reply, uint8_t *name) {
mdns_init_reply(reply, 0);
/* announce hostname and address */
reply->num_ans_rr += populate_answers(svr, &reply->rr_ans, name, RR_A);
reply->num_ans_rr += populate_answers(svr, &reply->rr_ans, name, RR_AAAA);
reply->num_ans_rr += populate_answers(svr, &reply->rr_ans, name, RR_PTR);
// remember to add the services dns-sd PTR too
reply->num_ans_rr += populate_answers(svr, &reply->rr_ans, SERVICES_DNS_SD_NLABEL, RR_PTR);
// see if we can match additional records for answers
add_related_rr(svr, reply->rr_ans, reply);
// additional records for additional records
add_related_rr(svr, reply->rr_add, reply);
}
static void process_for_probe(struct mdnsd *svr, struct mdns_pkt *mdns_packet)
{
mdns_init_query(mdns_packet, 0);
mdns_packet->num_qn += populate_probe(svr, &mdns_packet->rr_qn);
}
#endif /* !defined MDNS_NO_RESPONDER_SUPPORT */
static void process_for_query(struct mdnsd *svr, struct mdns_pkt *mdns_packet)
{
mdns_init_query(mdns_packet, 0);
mdns_packet->num_qn += populate_query(svr, &mdns_packet->rr_qn);
#ifndef MDNS_NO_RESPONDER_SUPPORT
// advertisement of my address to mdns neighbor
mdns_packet->num_ans_rr += populate_answers(svr, &mdns_packet->rr_add, svr->hostname, RR_A);
mdns_packet->num_ans_rr += populate_answers(svr, &mdns_packet->rr_add, svr->hostname, RR_AAAA);
#endif
}
static void update_cache(struct mdnsd *svr)
{
struct rr_list *remove_list = NULL;
pthread_mutex_lock(&svr->data_lock);
for (struct rr_group *group = svr->cache; group; group = group->next) {
for (struct rr_list *list = group->rr; list; list = list->next) {
struct rr_entry *entry = list->e;
if (entry) {
/* if ttl is expired or rr is RR_PTR or RR_SRV, remove rr from cache */
if (((time(NULL) - entry->update_time) > entry->ttl) || (svr->c_status != CACHE_SERVICE_DISCOVERY && (entry->type == RR_PTR || entry->type == RR_SRV))) {
rr_list_append(&remove_list, entry);
}
}
}
}
/* remove ttl expired entry */
for (struct rr_list *list = remove_list; list; list = list->next) {
struct rr_entry *entry = list->e;
if (entry) {
rr_group_del(&svr->cache, entry);
}
}
rr_list_destroy(remove_list, 0); /* destroy remove list */
pthread_mutex_unlock(&svr->data_lock);
}
static void add_rr_to_cache(struct mdnsd *svr, struct mdns_pkt *pkt)
{
struct rr_list *filtered_rr_list = NULL;
struct rr_list *srv_list = NULL;
struct rr_entry *a_e = NULL;
int b_found = 0;
pthread_mutex_lock(&svr->data_lock);
if (svr->c_status == CACHE_SLEEP) {
goto out_with_mutex;
}
/* filtering */
struct rr_list *rr_set[] = {
pkt->rr_ans,
pkt->rr_auth,
pkt->rr_add
};
for (int i = 0; i < sizeof(rr_set) / sizeof(rr_set[0]); i++) {
for (struct rr_list *rr_l = rr_set[i]; rr_l; rr_l = rr_l->next) {
struct rr_entry *rr_e = rr_l->e;
if (rr_e) {
if (rr_e->type == RR_A || rr_e->type == RR_AAAA) {
#ifndef MDNS_NO_RESPONDER_SUPPORT
b_found = 1;
rr_list_append(&filtered_rr_list, rr_e);
#else
if (svr->c_status == CACHE_RESOLVE_HOSTNAME) {
if (svr->c_filter) {
char *name = nlabel_to_str(rr_e->name);
if (strncmp(name, svr->c_filter, strlen(svr->c_filter)) == 0) {
b_found = 1;
rr_list_append(&filtered_rr_list, rr_e);
}
MDNS_FREE(name);
}
} else if (svr->c_status == CACHE_SERVICE_DISCOVERY) {
rr_list_append(&filtered_rr_list, rr_e);
}
#endif /* !defined MDNS_NO_RESPONDER_SUPPORT */
} else if (rr_e->type == RR_PTR) {
if (svr->c_status == CACHE_SERVICE_DISCOVERY) {
if (svr->c_filter) {
char *name = nlabel_to_str(rr_e->name);
if (strncmp(name, svr->c_filter, strlen(svr->c_filter)) == 0) {
b_found = 1;
rr_list_append(&filtered_rr_list, rr_e);
}
MDNS_FREE(name);
}
}
} else if (rr_e->type == RR_SRV) {
if (svr->c_status == CACHE_SERVICE_DISCOVERY) {
rr_list_append(&filtered_rr_list, rr_e);
}
}
}
}
}
if (!b_found) {
if (filtered_rr_list) {
rr_list_destroy(filtered_rr_list, 0);
filtered_rr_list = NULL;
}
goto out_with_mutex;
}
for (struct rr_list *rr_l = filtered_rr_list; rr_l; rr_l = rr_l->next) {
struct rr_entry *rr_e = rr_l->e;
if (rr_e) {
struct rr_entry *cached_rr_e = NULL;
struct rr_group *group = rr_group_find(svr->cache, rr_e->name);
if (group) {
struct rr_entry *rr_e_in_cache = rr_entry_match(group->rr, rr_e);
if (rr_e_in_cache) {
rr_group_del(&svr->cache, rr_e_in_cache);
if (rr_e->ttl > 0) {
cached_rr_e = rr_duplicate(rr_e);
rr_group_add(&svr->cache, cached_rr_e);
}
} else {
cached_rr_e = rr_duplicate(rr_e);
rr_group_add(&svr->cache, cached_rr_e);
}
} else {
cached_rr_e = rr_duplicate(rr_e);
rr_group_add(&svr->cache, cached_rr_e);
}
/* if SRV's target is null, add RR_A 's hostname to SRV's target */
if (cached_rr_e) {
if (cached_rr_e->type == RR_SRV) {
rr_list_append(&srv_list, cached_rr_e);
} else if ((a_e == NULL) && (cached_rr_e->type == RR_A || cached_rr_e->type == RR_AAAA)) {
a_e = cached_rr_e;
}
}
}
}
/* if SRV's target is null, add RR_A 's hostname to SRV's target */
if (srv_list) {
if (a_e && a_e->name) {
for (struct rr_list *rr_l = srv_list; rr_l; rr_l = rr_l->next) {
struct rr_entry *rr_e = rr_l->e;
if (rr_e && (rr_e->data.SRV.target == NULL)) {
rr_e->data.SRV.target = dup_nlabel(a_e->name);
}
}
}
rr_list_destroy(srv_list, 0);
srv_list = NULL;
}
if (filtered_rr_list) {
rr_list_destroy(filtered_rr_list, 0);
filtered_rr_list = NULL;
}
out_with_mutex:
pthread_mutex_unlock(&svr->data_lock);
return;
}
// processes the incoming MDNS packet
// returns >0 if processed, 0 otherwise
static int process_mdns_pkt(struct mdnsd *svr, struct mdns_pkt *pkt, struct sockaddr_in *fromtoaddr, struct mdns_pkt *reply) {
int result;
assert(pkt != NULL);
#ifndef MDNS_NO_RESPONDER_SUPPORT
// is it standard query?
if ((pkt->flags & MDNS_FLAG_RESP) == 0 &&
MDNS_FLAG_GET_OPCODE(pkt->flags) == 0) {
mdns_init_reply(reply, pkt->id);
DEBUG_PRINTF("flags = %04x, qn = %d, ans = %d, add = %d\n",
pkt->flags,
pkt->num_qn,
pkt->num_ans_rr,
pkt->num_add_rr);
int unicast = 0; /* 0, 1, 2, 4 = no, direct unicast, legacy, unicast-response */
if (fromtoaddr->sin_port != htons(MDNS_PORT)) {
/* legacy query */
unicast |= 2;
} else if (fromtoaddr->sin_addr.s_addr != inet_addr(MDNS_ADDR)) {
/* direct unicast - send back to same addr:port */
unicast |= 1;
}
// loop through questions
struct rr_list *qnl = pkt->rr_qn;
for (int i = 0; i < pkt->num_qn; i++, qnl = qnl->next) {
struct rr_entry *qn = qnl->e;
int num_ans_added = 0;
char *namestr = nlabel_to_str(qn->name);
DEBUG_PRINTF("qn #%d: type %s (%02x) %s - ", i, rr_get_type_name(qn->type), qn->type, namestr);
MDNS_FREE(namestr);
// check if it's a unicast query - we don't treat those differently
if (qn->unicast_query) {
DEBUG_PRINTF("treating unicast query as multicast\n");
unicast |= 4;
}
num_ans_added = populate_answers(svr, &reply->rr_ans, qn->name, qn->type);
reply->num_ans_rr += num_ans_added;
DEBUG_PRINTF("added %d answers\n", num_ans_added);
}
// remove our replies if they were already in their answers
struct rr_list *prev_ans = NULL;
for (struct rr_list *ans = reply->rr_ans; ans;) {
struct rr_list *next_ans = ans->next;
struct rr_entry *known_ans = rr_entry_match(pkt->rr_ans, ans->e);
// discard answers that have at least half of the actual TTL
if (known_ans != NULL && known_ans->ttl >= ans->e->ttl / 2) {
char *namestr = nlabel_to_str(ans->e->name);
DEBUG_PRINTF("removing answer for %s\n", namestr);
MDNS_FREE(namestr);
// check if list item is head
if (prev_ans == NULL)
reply->rr_ans = ans->next;
else
prev_ans->next = ans->next;
MDNS_FREE(ans);
ans = prev_ans;
// adjust answer count
reply->num_ans_rr--;
}
prev_ans = ans;
ans = next_ans;
}
// see if we can match additional records for answers
add_related_rr(svr, reply->rr_ans, reply);
// additional records for additional records
add_related_rr(svr, reply->rr_add, reply);
DEBUG_PRINTF("\n");
/* specific processing for unicast, ... or not */
if (unicast & 2) {
/* legacy: (rfc6762#section-5.4) send back to same addr:port, no cache flush, TTL -> 10s */
struct rr_list *rr_grps[] = { reply->rr_ans, reply->rr_add, NULL };
for (struct rr_list **rrl = rr_grps; *rrl; ++rrl) {
for (struct rr_list *ans = *rrl; ans; ans = ans->next) {
ans->e->cache_flush = 0;
ans->e->ttl = DEFAULT_TTL_LEGACY;
}
}
} else if (unicast & 1) {
/* direct: (rfc6762#section-5.5) should be sending answers to same addr:5353 if on LAN, then multicast */
/* let's pretend the first reply got lost ... */
fromtoaddr->sin_port = 0;
} else if (unicast & 4) {
/* unicast-response: (rfc6762#section-5.4) should be sending answers to QUs to same addr:5353 if on LAN, then multicast */
/* let's pretend the first reply got lost ... */
fromtoaddr->sin_port = 0;
} else {
/* send usual multicast response */
fromtoaddr->sin_port = 0;
}
return reply->num_ans_rr;
} else {
result = 0;
}
#else
result = 0;
#endif
// if there is answer, add rr cache
if (pkt->num_ans_rr || pkt->num_auth_rr || pkt->num_add_rr) {
update_cache(svr);
add_rr_to_cache(svr, pkt);
}
#ifdef MDNSD_RR_DEBUG
print_cache(svr);
#endif
#ifdef MDNSD_MEMORY_DEBUG
mdns_show_meminfo();
#endif
return result;
}
int create_pipe(int handles[2]) {
//#ifdef _WIN32
#if (PIPE_SOCKET_TYPE == 1)
int result = -1;
struct sockaddr_in serv_addr;
handles[0] = -1;
handles[1] = -1;
/* create 1st socket */
handles[0] = socket(AF_INET, SOCK_DGRAM, 0);
if (handles[0] == -1)
goto errout;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PIPE_SOCKET_PORT0);
serv_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
if (bind(handles[0], (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1)
goto errout;
if ((handles[1] = socket(PF_INET, SOCK_DGRAM, 0)) < 0) {
goto errout;
#if 0 /* this can be enabled for supporting bi-directional pipe */
/* create 2nd socket */
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PIPE_SOCKET_PORT1);
serv_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
if (bind(handles[1], (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1)
goto errout;
#endif
/* success */
result = 0;
return result;
errout:
for (int i = 0; i < 2; ++i) {
if (handles[i] != -1) {
close(handles[i]);
handles[i] = -1;
}
}
return result;
#else
return pipe(handles);
#endif
}
int read_pipe(int s, char* buf, int len) {
//#ifdef _WIN32
#if (PIPE_SOCKET_TYPE == 1)
struct sockaddr_in fromaddr;
socklen_t sockaddr_size = sizeof(struct sockaddr_in);
return recvfrom(s, buf, len, 0, (struct sockaddr *)&fromaddr, &sockaddr_size);
#else
return read(s, buf, len);
#endif
}
int write_pipe(int s, char *buf, int len, int port) {
//#ifdef _WIN32
#if (PIPE_SOCKET_TYPE == 1)
static struct sockaddr_in toaddr;
memset(&toaddr, 0, sizeof(struct sockaddr_in));
toaddr.sin_family = AF_INET;
toaddr.sin_port = htons(port);
toaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
return sendto(s, buf, len, 0, (struct sockaddr *)&toaddr, sizeof(struct sockaddr_in));
#else
return write(s, buf, len);
#endif
}
int close_pipe(int s) {
return close(s);
}
static int request_sendmsg(struct mdnsd *svr)
{
int ret;
struct timespec abstime;
const int wait_sec = 1;
const int wait_nsec = 0;
const int max_try_count = 3;
int try_count = 0;
int timeout = 0;
do {
ret = write_pipe(svr->notify_pipe[1], ".", 1, PIPE_SOCKET_PORT0);
if (ret == -1) {
log_message(LOG_ERR, "write_pipe() failed. (ret=%d)\n", ret);
continue;
}
(void)clock_gettime(CLOCK_REALTIME, &abstime);
abstime.tv_sec += wait_sec;
abstime.tv_nsec += wait_nsec;
if (abstime.tv_nsec >= NSEC_PER_SEC) {
abstime.tv_sec++;
abstime.tv_nsec -= NSEC_PER_SEC;
}
timeout = 0;
while (sem_timedwait(&svr->sendmsg_sem, &abstime) != 0) {
int err = get_errno();
assert(err == EINTR || err == ETIMEDOUT);
if (err == ETIMEDOUT) {
timeout = 1;
break;
}
}
if (timeout) {
log_message(LOG_ERR, "sem_timedwait() timeout.\n");
continue;
}
break;
} while (++try_count < max_try_count);
if (try_count == max_try_count) {
log_message(LOG_ERR, "request_sendmsg() failed.\n");
return -1;
}
return 0;
}
// main loop to receive, process and send out MDNS replies
// also handles MDNS service announces
static void main_loop(struct mdnsd *svr) {
fd_set sockfd_set;
int max_fd = 0;
char notify_buf[2]; // buffer for reading of notify_pipe
int ret;
int econnreset_count = 0;
struct mdns_pkt *mdns_packet = NULL;
void *pkt_buffer = MDNS_MALLOC(PACKET_SIZE);
if (pkt_buffer == NULL) {
log_message(LOG_ERR, "memory allocation : pkt_buffer\n");
goto out;
}
mdns_packet = MDNS_MALLOC(sizeof(struct mdns_pkt));
if (mdns_packet == NULL) {
log_message(LOG_ERR, "memory allocation : mdns_packet\n");
goto out;
}
memset(mdns_packet, 0, sizeof(struct mdns_pkt));
max_fd = svr->sockfd;
if (svr->notify_pipe[0] > max_fd)
max_fd = svr->notify_pipe[0];
while (1) {
struct sockaddr_in fromaddr;
socklen_t sockaddr_size = sizeof(struct sockaddr_in);
svr->sendmsg_requested = 0;
FD_ZERO(&sockfd_set);
FD_SET(svr->sockfd, &sockfd_set);
FD_SET(svr->notify_pipe[0], &sockfd_set);
ret = select(max_fd + 1, &sockfd_set, NULL, NULL, NULL);
if (ret > 0) {
if (FD_ISSET(svr->notify_pipe[0], &sockfd_set)) {
svr->sendmsg_requested = 1;
// flush the notify_pipe
if (read_pipe(svr->notify_pipe[0], (char *)&notify_buf, 1) == -1)
log_message(LOG_ERR, "read_pipe() failed; %s\n", strerror(errno));
} else if (FD_ISSET(svr->sockfd, &sockfd_set)) {
ssize_t recvsize = recvfrom(svr->sockfd, pkt_buffer, PACKET_SIZE, 0,
(struct sockaddr *) &fromaddr, &sockaddr_size);
if (recvsize < 0) {
int errval = errno;
log_message(LOG_ERR, "recv failed(): recvsize: %d; %s\n", recvsize, strerror(errno));
if (errval == ECONNRESET) {
econnreset_count++;
if (econnreset_count >= MAX_ECONNRESET_COUNT) {
int remaining_cnt;
log_message(LOG_ERR, "ECONNRESET occurred %d times. recv socket will be recreated.\n", econnreset_count);
/* close and recreate recv socket */
close(svr->sockfd);
remaining_cnt = 3;
while (remaining_cnt) {
svr->sockfd = create_recv_sock(svr->domain);
if (svr->sockfd != -1) {
/* succeed to create recv socket */
break;
}
remaining_cnt--;
}
if (svr->sockfd == -1) {
log_message(LOG_ERR, "fail to recreate recv socket. mdnsd main_loop will be terminated.\n");
break; /* exit loop */
}
if (svr->sockfd > max_fd) {
max_fd = svr->sockfd;
}
econnreset_count = 0;
}
}
continue;
}
DEBUG_PRINTF("data from=%s:%d size=%ld\n", inet_ntoa(fromaddr.sin_addr), ntohl(fromaddr.sin_port), (long) recvsize);
struct mdns_pkt *mdns = mdns_parse_pkt(pkt_buffer, recvsize);
if (mdns != NULL) {
struct sockaddr_in toaddr = fromaddr;
if (process_mdns_pkt(svr, mdns, &toaddr, mdns_packet)) {
#ifndef MDNS_NO_RESPONDER_SUPPORT
size_t replylen = mdns_encode_pkt(mdns_packet, pkt_buffer, PACKET_SIZE);
ssize_t ret = -1;
if (toaddr.sin_port != 0) {
ret = send_packet_to(svr->sockfd, pkt_buffer, replylen, &toaddr);
} else {
ret = send_packet(svr->sockfd, pkt_buffer, replylen, svr->domain);
}
if (ret == -1)
log_message(LOG_ERR, "send_packet() failed; %s\n", strerror(errno));
#endif
} else if (mdns->num_qn == 0) {
DEBUG_PRINTF("(no questions in packet)\n\n");
}
mdns_pkt_destroy(mdns);
}
}
} else {
log_message(LOG_ERR, "select() failed (ret: %d); %s\n", ret, strerror(errno));
continue;
}
// send out query
while (1) {
if (!svr->query) {
break;
}
process_for_query(svr, mdns_packet);
if (mdns_packet->num_qn > 0) {
DEBUG_PRINTF("sending query : (num of qn = %d)\n", mdns_packet->num_qn);
size_t replylen = mdns_encode_pkt(mdns_packet, pkt_buffer, PACKET_SIZE);
if (send_packet(svr->sockfd, pkt_buffer, replylen, svr->domain) == -1) {
log_message(LOG_ERR, "send_packet() failed. (errno: %d)\n", errno);
}
if (mdns_packet->rr_qn) {
rr_list_destroy(mdns_packet->rr_qn, 1);
mdns_packet->rr_qn = NULL;
}
}
}
#ifndef MDNS_NO_RESPONDER_SUPPORT
// send out probe
while (1) {
if (!svr->probe) {
break;
}
process_for_probe(svr, mdns_packet);
if (mdns_packet->num_qn > 0) {
DEBUG_PRINTF("sending query for probe : (num of qn = %d)\n", mdns_packet->num_qn);
size_t replylen = mdns_encode_pkt(mdns_packet, pkt_buffer, PACKET_SIZE);
if (send_packet(svr->sockfd, pkt_buffer, replylen, svr->domain) == -1) {
log_message(LOG_ERR, "send_packet() failed; %s\n", strerror(errno));
}
if (mdns_packet->rr_qn) {
rr_list_destroy(mdns_packet->rr_qn, 1);
mdns_packet->rr_qn = NULL;
}
}
}
// send out announces
while (1) {
struct rr_entry *ann_e = NULL;
// extract from head of list
pthread_mutex_lock(&svr->data_lock);
if (svr->announce)
ann_e = rr_list_remove(&svr->announce, svr->announce->e);
pthread_mutex_unlock(&svr->data_lock);
if (! ann_e)
break;
char *namestr = nlabel_to_str(ann_e->name);
DEBUG_PRINTF("sending announce for %s\n", namestr);
MDNS_FREE(namestr);
announce_srv(svr, mdns_packet, ann_e->name);
if (mdns_packet->num_ans_rr > 0) {
size_t replylen = mdns_encode_pkt(mdns_packet, pkt_buffer, PACKET_SIZE);
if (send_packet(svr->sockfd, pkt_buffer, replylen, svr->domain) == -1)
log_message(LOG_ERR, "send_packet() failed; %s\n", strerror(errno));
}
}
#endif /* !defined MDNS_NO_RESPONDER_SUPPORT */
if (svr->sendmsg_requested) {
if (svr->stop_flag)
break; /* exit main_loop */
sem_post(&svr->sendmsg_sem);
}
if (svr->dump_cache) {
svr->dump_cache = 0;
print_cache(svr);
}
}
if (svr->sendmsg_requested && svr->stop_flag) {
// sem_post() should be executed in order to awaken mdnsd_stop(),
// because main_loop is terminated before executing sem_post()
sem_post(&svr->sendmsg_sem);
}
// main thread terminating. send out "goodbye packets" for services
mdns_init_reply(mdns_packet, 0);
#ifndef MDNS_NO_RESPONDER_SUPPORT
pthread_mutex_lock(&svr->data_lock);
for (struct rr_list *svc_le = svr->services; svc_le; svc_le = svc_le->next) {
// set TTL to zero
svc_le->e->ttl = 0;
mdns_packet->num_ans_rr += rr_list_append(&mdns_packet->rr_ans, svc_le->e);
}
pthread_mutex_unlock(&svr->data_lock);
#endif
// send out packet
if (mdns_packet->num_ans_rr > 0) {
if (svr->sockfd != -1) {
size_t replylen = mdns_encode_pkt(mdns_packet, pkt_buffer, PACKET_SIZE);
if (send_packet(svr->sockfd, pkt_buffer, replylen, svr->domain) == -1) {
log_message(LOG_ERR, "send_packet() failed; %s\n", strerror(errno));
}
}
}
out:
// destroy packet
if (mdns_packet) {
mdns_init_reply(mdns_packet, 0);
MDNS_FREE(mdns_packet);
mdns_packet = NULL;
}
if (pkt_buffer) {
MDNS_FREE(pkt_buffer);
pkt_buffer = NULL;
}
if (svr->sockfd != -1) {
close(svr->sockfd);
svr->sockfd = -1;
}
svr->stop_flag = 2;
}
#ifndef MDNS_NO_RESPONDER_SUPPORT
static int probe_hostname(struct mdnsd *svr, char *hostname)
{
int result = 1;
int i;
struct rr_entry *probe_e = NULL;
uint8_t *name;
pthread_mutex_lock(&svr->data_lock);
svr->c_status = CACHE_RESOLVE_HOSTNAME;
if (svr->c_filter) {
MDNS_FREE(svr->c_filter);
svr->c_filter = MDNS_STRDUP(hostname);
}
pthread_mutex_unlock(&svr->data_lock);
for (i = 0; i < 3; i++) {
/* make query with RR_ANY for probe hostname */
name = create_nlabel(hostname);
probe_e = qn_create(name, RR_ANY, 0);
pthread_mutex_lock(&svr->data_lock);
rr_list_append(&svr->probe, probe_e);
pthread_mutex_unlock(&svr->data_lock);
request_sendmsg(svr);
usleep(250 * 1000); // 250ms delay
if (lookup_hostname(svr, hostname) == 0) {
result = 0;
break;
}
}
pthread_mutex_lock(&svr->data_lock);
svr->c_status = CACHE_NORMAL;
if (svr->c_filter) {
MDNS_FREE(svr->c_filter);
svr->c_filter = NULL;
}
pthread_mutex_unlock(&svr->data_lock);
return result;
}
#endif
static void init_service_discovery_result(struct mdns_service_info
*service_list, int num_of_services)
{
if (service_list == NULL) {
log_message(LOG_ERR, "mdns_service_info is null.\n");
return;
}
for (int i = 0; i < num_of_services; i++) {
service_list[i].type = NULL;
service_list[i].instance_name = NULL;
service_list[i].hostname = NULL;
service_list[i].ipaddr = 0;
service_list[i].port = 0;
}
}
static void clear_service_discovery_result(struct mdns_service_info
*service_list, int num_of_services)
{
if (service_list == NULL) {
log_message(LOG_ERR, "service_list is null.\n");
return;
}
for (int i = 0; i < num_of_services; i++) {
if (service_list[i].type) {
MDNS_FREE(service_list[i].type);
service_list[i].type = NULL;
}
if (service_list[i].instance_name) {
MDNS_FREE(service_list[i].instance_name);
service_list[i].instance_name = NULL;
}
if (service_list[i].hostname) {
MDNS_FREE(service_list[i].hostname);
service_list[i].hostname = NULL;
}
service_list[i].ipaddr = 0;
service_list[i].port = 0;
}
}
static void destroy_service_discovery_result(struct mdns_service_info
*service_list, int num_of_services)
{
if (service_list == NULL) {
log_message(LOG_ERR, "service_list is null.\n");
return;
}
clear_service_discovery_result(service_list, num_of_services);
MDNS_FREE(service_list);
}
#ifndef MDNS_NO_RESPONDER_SUPPORT
static int mdnsd_set_host_info(struct mdnsd *svr, const char *hostname, uint32_t ipaddr)
{
int result = -1;
struct rr_entry *a_e = NULL, *nsec_e = NULL;
int domain;
char mdns_suffix[16];
if (svr == NULL) {
log_message(LOG_ERR, "mdnsd instance handle is null.\n");
goto done;
}
domain = check_mdns_domain(hostname);
if (domain == MDNS_DOMAIN_LOCAL) {
snprintf(mdns_suffix, sizeof(mdns_suffix)-1, MDNS_SUFFIX_LOCAL);
}
#if defined(CONFIG_NETUTILS_MDNS_XMDNS)
else if (domain == MDNS_DOMAIN_SITE) {
snprintf(mdns_suffix, sizeof(mdns_suffix)-1, MDNS_SUFFIX_SITE);
}
#endif
else {
log_message(LOG_ERR, "mdnsd hostname is invalid.\n");
goto done;
}
if (ipaddr == 0) {
log_message(LOG_ERR, "mdnsd ip address is not set.\n");
goto done;
}
int need_probe = 1;
uint8_t *hname_nlabel = create_nlabel(hostname);
if (svr->hostname && svr->hostname_org) {
if ((strcmp((char *)hname_nlabel, (char *)svr->hostname_org) == 0) || (strcmp((char *)hname_nlabel, (char *)svr->hostname) == 0)) {
need_probe = 0;
}
}
MDNS_FREE(hname_nlabel);
char hname[128];
if (need_probe) {
// probe hostname collision
char *str;
snprintf(hname, sizeof(hname), hostname);
int alternative_hostname = 0;
int no = 2;
while (probe_hostname(svr, hname) == 0) {
/* when name conflict occurs */
snprintf(hname, sizeof(hname), hostname);
str = strstr(hname, mdns_suffix);
if (str) {
snprintf(str, sizeof(hname), "(%d)%s", no++, mdns_suffix);
alternative_hostname = 1;
} else {
log_message(LOG_ERR, "cannot set an alternative hostname.\n");
}
}
// if there is previous hostname information, delete it
if (svr->hostname) {
if (svr->hostname == svr->hostname_org) {
MDNS_FREE(svr->hostname);
svr->hostname = NULL;
svr->hostname_org = NULL;
} else {
MDNS_FREE(svr->hostname);
svr->hostname = NULL;
MDNS_FREE(svr->hostname_org);
svr->hostname_org = NULL;
}
}
// set hostname information
if (alternative_hostname) {
svr->hostname = create_nlabel(hname);
svr->hostname_org = create_nlabel(hostname);
} else {
svr->hostname = create_nlabel(hname);
svr->hostname_org = svr->hostname;
}
}
char *hname_str = nlabel_to_str(svr->hostname);
int hname_strlen = strlen(hname_str);
hname_str[hname_strlen - 1] = '\0';
a_e = rr_create_a(create_nlabel(hname_str), ipaddr);
nsec_e = rr_create(create_nlabel(hname_str), RR_NSEC);
rr_set_nsec(nsec_e, RR_A);
MDNS_FREE(hname_str);
pthread_mutex_lock(&svr->data_lock);
rr_group_add(&svr->group, a_e);
rr_group_add(&svr->group, nsec_e);
// append RR_A entry to announce list
rr_list_append(&svr->announce, a_e);
pthread_mutex_unlock(&svr->data_lock);
request_sendmsg(svr);
/* success */
result = 0;
done:
return result;
}
/*
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <netinet/in.h>
#include <arpa/inet.h>
static const char * flags(int sd, const char * name)
{
static char buf[1024];
static struct ifreq ifreq;
strcpy(ifreq.ifr_name, name);
int r = ioctl(sd, SIOCGIFFLAGS, (char *)&ifreq);
assert(r == 0);
int l = 0;
#define FLAG(b) if(ifreq.ifr_flags & b) l += snprintf(buf + l, sizeof(buf) - l, #b " ")
FLAG(IFF_UP);
FLAG(IFF_BROADCAST);
FLAG(IFF_DEBUG);
FLAG(IFF_LOOPBACK);
FLAG(IFF_POINTOPOINT);
FLAG(IFF_RUNNING);
FLAG(IFF_NOARP);
FLAG(IFF_PROMISC);
FLAG(IFF_NOTRAILERS);
FLAG(IFF_ALLMULTI);
FLAG(IFF_MASTER);
FLAG(IFF_SLAVE);
FLAG(IFF_MULTICAST);
FLAG(IFF_PORTSEL);
FLAG(IFF_AUTOMEDIA);
FLAG(IFF_DYNAMIC);
#undef FLAG
return buf;
}
*/
#ifndef HAVE_NETLIB_GET_IPV4ADDR
int netlib_get_ipv4addr(const char *ifname, struct in_addr *addr) {
int result = -1;
int sd;
struct ifreq ifreqs[8];
struct ifconf ifconf = {0};
ifconf.ifc_req = ifreqs;
ifconf.ifc_len = sizeof(ifreqs);
sd = socket(PF_INET, SOCK_STREAM, 0);
do {
if (sd < 0) break;
int r = ioctl(sd, SIOCGIFCONF, (char *)&ifconf);
if (r != 0) break;
for (int i = 0; i < ifconf.ifc_len/sizeof(struct ifreq); ++i)
{
/* printf("%s: %s\n", ifreqs[i].ifr_name, inet_ntoa(((struct sockaddr_in *)&ifreqs[i].ifr_addr)->sin_addr));
printf(" flags: %s\n", flags(sd, ifreqs[i].ifr_name));
*/
if (0 == strcmp( ifname, ifreqs[i].ifr_name)) {
*addr = ((struct sockaddr_in *)&ifreqs[i].ifr_addr)->sin_addr;
result = 0;
break;
}
}
} while (0);
close(sd);
return result;
}
#endif
static int mdnsd_set_host_info_by_netif(struct mdnsd *svr, const char *hostname, const char *netif_nameOrAddress)
{
int result = -1;
struct in_addr ipaddr;
if (svr == NULL) {
log_message(LOG_ERR, "mdnsd instance handle is null.\n");
goto done;
}
if ((in_addr_t)(-1) == (ipaddr.s_addr = inet_addr(netif_nameOrAddress))) {
result = netlib_get_ipv4addr(netif_nameOrAddress, &ipaddr);
if (result < 0)
return result;
}
result = mdnsd_set_host_info(svr, hostname, ipaddr.s_addr);
done:
return result;
}
#endif /* !defined MDNS_NO_RESPONDER_SUPPORT */
static int init_mdns_context(int domain)
{
int result = -1;
pthread_t tid;
pthread_attr_t attr;
if (g_svr) {
log_message(LOG_ERR, "mdnsd is running.\n");
goto out;
}
g_svr = MDNS_MALLOC(sizeof(struct mdnsd));
if (g_svr == NULL) {
log_message(LOG_ERR, "memory allocation failed.\n");
goto errout;
}
/* initialize struct mdnsd instance */
memset(g_svr, 0, sizeof(struct mdnsd));
g_svr->stop_flag = 0;
g_svr->sockfd = -1;
g_svr->notify_pipe[0] = -1;
g_svr->notify_pipe[1] = -1;
switch (domain) {
#if defined(CONFIG_NETUTILS_MDNS_XMDNS)
case MDNS_DOMAIN_SITE:
#endif
case MDNS_DOMAIN_LOCAL:
g_svr->domain = domain;
break;
case MDNS_DOMAIN_UNKNOWN:
default:
log_message(LOG_ERR, "invalid domain type.\n");
goto errout;
}
if (create_pipe(g_svr->notify_pipe) != 0) {
log_message(LOG_ERR, "create_pipe() failed.\n");
goto errout;
}
g_svr->sockfd = create_recv_sock(domain);
if (g_svr->sockfd < 0) {
log_message(LOG_ERR, "create_recv_sock() failed.\n");
goto errout;
}
/* memory allocation for service discovery */
if (g_service_list == NULL) {
g_service_list = (struct mdns_service_info *)MDNS_MALLOC(sizeof(struct mdns_service_info) * MAX_NUMBER_OF_SERVICE_DISCOVERY_RESULT);
if (g_service_list == NULL) {
goto errout;
}
init_service_discovery_result(g_service_list, MAX_NUMBER_OF_SERVICE_DISCOVERY_RESULT);
}
pthread_mutex_init(&g_svr->data_lock, NULL);
sem_init(&g_svr->sendmsg_sem, 0, 0);
g_svr->sendmsg_requested = 1;
g_svr->dump_cache= 0;
/* init thread */
if (pthread_attr_init(&attr) != 0) {
log_message(LOG_ERR, "pthread_attr_init() failed.\n");
goto errout_with_mutex;
}
#ifdef PTHREAD_CREATE_DETACHED_SUPPORTED /* PTHREAD_CREATE_DETACHED is not supported in tinyara */
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
#endif
size_t stksiz;
if (pthread_attr_getstacksize(&attr, &stksiz) == 0 && stksiz < THREAD_MAINLOOP_STACKSIZE) {
if (pthread_attr_setstacksize(&attr, THREAD_MAINLOOP_STACKSIZE) != 0) {
log_message(LOG_ERR, "pthread_attr_setstacksize() failed.\n");
goto errout_with_mutex;
}
}
/* create main_loop thread */
if ((pthread_create(&tid, &attr, (void * (*)(void *))main_loop, (void *)g_svr) != 0)) {
log_message(LOG_ERR, "pthread_create() failed.\n");
goto errout_with_mutex;
}
#ifdef PTHREAD_SETNAME_SUPPORTED
pthread_setname_np(tid, THREAD_MAINLOOP_NAME);
#endif
#ifndef PTHREAD_CREATE_DETACHED_SUPPORTED
if (pthread_detach(tid) != 0) {
log_message(LOG_ERR, "pthread_detach() failed.\n");
}
#endif
/* wait until main_loop starts */
while (g_svr->sendmsg_requested == 1) {
if (g_svr->stop_flag) {
goto errout_with_mutex;
}
usleep(10 * 1000);
}
#ifndef MDNS_NO_RESPONDER_SUPPORT
g_svr->c_status = CACHE_NORMAL;
#else
g_svr->c_status = CACHE_SLEEP;
#endif
/* success */
result = 0;
out:
return result;
errout_with_mutex:
if (g_svr) {
pthread_mutex_destroy(&g_svr->data_lock);
sem_destroy(&g_svr->sendmsg_sem);
}
errout:
if (g_svr) {
if (g_svr->sockfd != -1) {
close(g_svr->sockfd);
g_svr->sockfd = -1;
}
if (g_svr->notify_pipe[0] != -1) {
close_pipe(g_svr->notify_pipe[0]);
g_svr->notify_pipe[0] = -1;
}
if (g_svr->notify_pipe[1] != -1) {
close_pipe(g_svr->notify_pipe[1]);
g_svr->notify_pipe[1] = -1;
}
MDNS_FREE(g_svr);
g_svr = NULL;
}
if (g_service_list) {
destroy_service_discovery_result(g_service_list, MAX_NUMBER_OF_SERVICE_DISCOVERY_RESULT);
g_service_list = NULL;
}
return result;
}
static void release_mdns_context_resource(void)
{
if (g_svr->notify_pipe[0] != -1) {
close_pipe(g_svr->notify_pipe[0]);
g_svr->notify_pipe[0] = -1;
}
if (g_svr->notify_pipe[1] != -1) {
close_pipe(g_svr->notify_pipe[1]);
g_svr->notify_pipe[1] = -1;
}
pthread_mutex_destroy(&g_svr->data_lock);
sem_destroy(&g_svr->sendmsg_sem);
rr_group_destroy(g_svr->cache);
g_svr->cache = NULL;
rr_list_destroy(g_svr->query, 0);
g_svr->query = NULL;
if (g_svr->c_filter) {
MDNS_FREE(g_svr->c_filter);
g_svr->c_filter = NULL;
}
#ifndef MDNS_NO_RESPONDER_SUPPORT
rr_group_destroy(g_svr->group);
g_svr->group = NULL;
rr_list_destroy(g_svr->announce, 0);
g_svr->announce = NULL;
rr_list_destroy(g_svr->services, 0);
g_svr->services = NULL;
rr_list_destroy(g_svr->probe, 0);
g_svr->probe = NULL;
if (g_svr->hostname == g_svr->hostname_org) {
if (g_svr->hostname) {
MDNS_FREE(g_svr->hostname);
g_svr->hostname = NULL;
g_svr->hostname_org = NULL;
}
} else {
if (g_svr->hostname) {
MDNS_FREE(g_svr->hostname);
g_svr->hostname = NULL;
}
if (g_svr->hostname_org) {
MDNS_FREE(g_svr->hostname_org);
g_svr->hostname_org = NULL;
}
}
if (g_service_list) {
/* free memory for service discovery */
destroy_service_discovery_result(g_service_list, MAX_NUMBER_OF_SERVICE_DISCOVERY_RESULT);
g_service_list = NULL;
}
#endif /* !defined MDNS_NO_RESPONDER_SUPPORT */
g_svr->domain = MDNS_DOMAIN_UNKNOWN;
MDNS_FREE(g_svr);
g_svr = NULL;
}
static int destroy_mdns_context(void)
{
int result = -1;
if (g_svr == NULL) {
log_message(LOG_ERR, "mdnsd is not running.\n");
goto done;
}
g_svr->stop_flag = 1;
request_sendmsg(g_svr);
while (g_svr->stop_flag != 2) {
usleep(500 * 1000);
}
release_mdns_context_resource();
/* success */
result = 0;
done:
#ifdef MDNSD_MEMORY_DEBUG
mdns_show_meminfo();
#endif
return result;
}
static void mdns_cmd_mutex_lock(void)
{
if (!g_cmd_lock_initialized) {
if (pthread_mutex_init(&g_cmd_lock, NULL) != 0) {
log_message(LOG_ERR, "pthread_mutex_init() failed.\n");
return;
}
g_cmd_lock_initialized = 1;
}
if (g_cmd_lock_initialized) {
pthread_mutex_lock(&g_cmd_lock);
}
}
static void mdns_cmd_mutex_unlock(void)
{
if (g_cmd_lock_initialized) {
pthread_mutex_unlock(&g_cmd_lock);
}
}
static void handler_dump_cache(int sig) {
(void)sig;
g_svr->dump_cache = 1;
}
/////////////////////////////////////////////////////
// Public Functions
/////////////////////////////////////////////////////
#ifndef MDNS_NO_RESPONDER_SUPPORT
/****************************************************************************
* Name: mdnsd_start
*
* Description:
* Starts the MDNS daemon.
*
* Parameters:
* desired_hostname : the desired host name as string type. if same name is in the network,
* an alternative name will be set as hostname.
* netif_name : network interface name as string type
*
* Returned Value:
* On success, 0 is returned. On failure, a negative value is returned.
*
****************************************************************************/
int mdnsd_start(const char *desired_hostname, const char *netif_nameOrAddress)
{
int result = -1;
int domain;
mdns_cmd_mutex_lock();
domain = check_mdns_domain(desired_hostname);
if (init_mdns_context(domain) != 0) {
goto out_with_mutex;
}
/* set hostname and ip address */
if (mdnsd_set_host_info_by_netif(g_svr, desired_hostname, netif_nameOrAddress) != 0) {
if (destroy_mdns_context() != 0) {
log_message(LOG_ERR, "mdnsd_set_host_info_by_netif() and destroy_mdns_context() failed.\n");
}
goto out_with_mutex;
}
signal(SIGUSR1, handler_dump_cache);
/* success */
result = 0;
out_with_mutex:
mdns_cmd_mutex_unlock();
return result;
}
/****************************************************************************
* Name: mdnsd_stop
*
* Description:
* Stops the MDNS daemon.
*
* Returned Value:
* On success, 0 is returned. On failure, a negative value is returned.
*
****************************************************************************/
int mdnsd_stop(void)
{
int result = -1;
mdns_cmd_mutex_lock();
if (destroy_mdns_context() != 0) {
goto out_with_mutex;
}
/* success */
result = 0;
out_with_mutex:
mdns_cmd_mutex_unlock();
return result;
}
/****************************************************************************
* Name: mdnsd_get_hostname
*
* Description:
* Gets the current host name as MDNS type.
*
* Parameters:
* hostname_result : 32-bytes string buffer for the host name result
*
* Returned Value:
* On success, 0 is returned. On failure, a negative value is returned.
*
****************************************************************************/
int mdnsd_get_hostname(char *hostname_result)
{
int result = -1;
if (g_svr == NULL) {
log_message(LOG_ERR, "mdnsd is not running.\n");
goto out;
}
mdns_cmd_mutex_lock();
char *hname = nlabel_to_str(g_svr->hostname);
int hname_len = strlen(hname);
hname_len = (hname_len > 32) ? 32 : hname_len;
hname[hname_len - 1] = '\0';
strncpy(hostname_result, hname, 32);
MDNS_FREE(hname);
/* success */
result = 0;
mdns_cmd_mutex_unlock();
out:
return result;
}
static void mdns_service_destroy(struct mdns_service *srv)
{
assert(srv != NULL);
rr_list_destroy(srv->entries, 0);
MDNS_FREE(srv);
}
int mdnsd_register_service(const char *instance_name,
const char *type, uint16_t port, const char *hostname, const char *txt[]) {
struct rr_entry *txt_e = NULL,
*srv_e = NULL,
*ptr_e = NULL,
*bptr_e = NULL;
uint8_t *target;
uint8_t *inst_nlabel, *type_nlabel, *nlabel;
struct mdns_service *service = NULL;
if (g_svr == NULL) {
log_message(LOG_ERR, "mdnsd is not running.\n");
goto out;
}
if (check_mdns_domain(type) == MDNS_DOMAIN_UNKNOWN) {
log_message(LOG_ERR, "service type is invalid. service type should be "
#if defined(CONFIG_NETUTILS_MDNS_XMDNS)
"%s or %s domain.\n", MDNS_SUFFIX_LOCAL, MDNS_SUFFIX_SITE);
#else
"%s domain.\n", MDNS_SUFFIX_LOCAL);
#endif
goto out;
}
mdns_cmd_mutex_lock();
service = (struct mdns_service *)MDNS_MALLOC(sizeof(struct mdns_service));
if (service == NULL) {
log_message(LOG_ERR, "memory allocation failed.\n");
goto out_with_mutex;
}
memset(service, 0, sizeof(struct mdns_service));
// combine service name
type_nlabel = create_nlabel(type);
inst_nlabel = create_nlabel(instance_name);
nlabel = join_nlabel(inst_nlabel, type_nlabel);
// create TXT record
if (txt && *txt) {
txt_e = rr_create(dup_nlabel(nlabel), RR_TXT);
rr_list_append(&service->entries, txt_e);
// add TXTs
for (; *txt; txt++)
rr_add_txt(txt_e, *txt);
}
// create SRV record
assert(hostname || g_svr->hostname); // either one as target
target = hostname ?
create_nlabel(hostname) :
dup_nlabel(g_svr->hostname);
srv_e = rr_create_srv(dup_nlabel(nlabel), port, target);
rr_list_append(&service->entries, srv_e);
// create PTR record for type
ptr_e = rr_create_ptr(type_nlabel, srv_e);
// create services PTR record for type
// this enables the type to show up as a "service"
bptr_e = rr_create_ptr(dup_nlabel(SERVICES_DNS_SD_NLABEL), ptr_e);
// modify lists here
pthread_mutex_lock(&g_svr->data_lock);
if (txt_e)
rr_group_add(&g_svr->group, txt_e);
rr_group_add(&g_svr->group, srv_e);
rr_group_add(&g_svr->group, ptr_e);
rr_group_add(&g_svr->group, bptr_e);
// append PTR entry to announce list
rr_list_append(&g_svr->announce, ptr_e);
rr_list_append(&g_svr->services, ptr_e);
pthread_mutex_unlock(&g_svr->data_lock);
// don't free type_nlabel - it's with the PTR record
MDNS_FREE(nlabel);
MDNS_FREE(inst_nlabel);
// notify server
request_sendmsg(g_svr);
mdns_service_destroy(service);
/* result is success */
int result = 0;
out_with_mutex:
mdns_cmd_mutex_unlock();
out:
return result;
}
void mdnsd_set_hostname(struct mdnsd *svr, const char *hostname, uint32_t ip) {
struct rr_entry *a_e = NULL,
*nsec_e = NULL;
// currently can't be called twice
// dont ask me what happens if the IP changes
assert(svr->hostname == NULL);
a_e = rr_create_a(create_nlabel(hostname), ip);
nsec_e = rr_create(create_nlabel(hostname), RR_NSEC);
rr_set_nsec(nsec_e, RR_A);
pthread_mutex_lock(&svr->data_lock);
svr->hostname = create_nlabel(hostname);
rr_group_add(&svr->group, a_e);
rr_group_add(&svr->group, nsec_e);
pthread_mutex_unlock(&svr->data_lock);
}
void mdnsd_add_rr(struct mdnsd *svr, struct rr_entry *rr) {
pthread_mutex_lock(&svr->data_lock);
rr_group_add(&svr->group, rr);
pthread_mutex_unlock(&svr->data_lock);
}
#endif /* !defined MDNS_NO_RESPONDER_SUPPORT */
/****************************************************************************
* Name: mdnsd_resolve_hostname
*
* Description:
* Gets ip address with the given hostname.
*
* Parameters:
* hostname : host name as string type
* ipaddr : the pointer of ip address result
*
* Returned Value:
* On success, 0 is returned. On failure, a negative value is returned.
*
****************************************************************************/
int mdnsd_resolve_hostname(char *hostname, uint32_t *ipaddr)
{
int result = -1;
struct timeval old_time, cur_time;
int try_count;
int domain;
#ifndef MDNS_NO_RESPONDER_SUPPORT
if (g_svr == NULL) {
log_message(LOG_ERR, "mdnsd is not running.\n");
goto out;
}
#endif
domain = check_mdns_domain(hostname);
if (domain == MDNS_DOMAIN_UNKNOWN) {
log_message(LOG_ERR, "hostname is invalid. hostname should be "
#if defined(CONFIG_NETUTILS_MDNS_XMDNS)
"%s or %s domain.\n", MDNS_SUFFIX_LOCAL, MDNS_SUFFIX_SITE);
#else
"%s domain.\n", MDNS_SUFFIX_LOCAL);
#endif
goto out;
}
mdns_cmd_mutex_lock();
#ifndef MDNS_NO_RESPONDER_SUPPORT
if (g_svr->stop_flag == 2) {
/* mdnsd main_loop has terminated by itself */
log_message(LOG_ERR, "main_loop has been terminated. mdnsd will stop.\n");
release_mdns_context_resource();
goto out_with_mutex;
}
#else
if (init_mdns_context(domain) != 0) {
log_message(LOG_ERR, "init_mdns_context() failed.\n");
goto out_with_mutex;
}
pthread_mutex_lock(&g_svr->data_lock);
g_svr->c_status = CACHE_RESOLVE_HOSTNAME;
if (g_svr->c_filter) {
MDNS_FREE(g_svr->c_filter);
g_svr->c_filter = NULL;
}
g_svr->c_filter = MDNS_STRDUP(hostname);
pthread_mutex_unlock(&g_svr->data_lock);
#endif
if (lookup_hostname_to_addr(g_svr, hostname, ipaddr) == 0) {
result = 0;
#ifndef MDNS_NO_RESPONDER_SUPPORT
goto out_with_mutex;
#else
goto out_with_context;
#endif
}
try_count = 0;
TIME_GET(old_time);
do {
if (try_count < MDNS_HOSTNAME_RESOLVER_MAX_TRY_COUNT) {
struct rr_entry *a_e = NULL;
struct rr_entry *aaaa_e = NULL;
a_e = qn_create(create_nlabel(hostname), RR_A, 0);
aaaa_e = qn_create(create_nlabel(hostname), RR_AAAA, 0);
pthread_mutex_lock(&g_svr->data_lock);
rr_list_append(&g_svr->query, a_e);
rr_list_append(&g_svr->query, aaaa_e);
pthread_mutex_unlock(&g_svr->data_lock);
request_sendmsg(g_svr);
try_count++;
}
usleep(MDNS_HOSTNAME_RESOLVER_WAIT_TIME_MSEC * 1000);
if (lookup_hostname_to_addr(g_svr, hostname, ipaddr) == 0) {
result = 0;
break;
}
} while ((TIME_GET(cur_time),TIME_DIFF_USEC(old_time, cur_time)) <= (MDNS_HOSTNAME_RESOLVER_TIMEOUT_MSEC * 1000));
#ifdef MDNS_NO_RESPONDER_SUPPORT
out_with_context:
pthread_mutex_lock(&g_svr->data_lock);
g_svr->c_status = CACHE_SLEEP;
if (g_svr->c_filter) {
MDNS_FREE(g_svr->c_filter);
g_svr->c_filter = NULL;
}
pthread_mutex_unlock(&g_svr->data_lock);
if (destroy_mdns_context() != 0) {
log_message(LOG_ERR, "destroy_mdns_context() failed.\n");
}
#endif
out_with_mutex:
mdns_cmd_mutex_unlock();
out:
return result;
}
/****************************************************************************
* Name: mdnsd_discover_service
*
* Description:
* Discovers services with the given service type string
*
* Parameters:
* service_type : mdns service type string
* discover_time_msec : time in milliseconds for discovering service
* service_list : the array of service list
* num_of_services : number of services
*
* Returned Value:
* On success, 0 is returned. On failure, a negative value is returned.
*
****************************************************************************/
int mdnsd_discover_service(char *service_type, int discover_time_msec, struct mdns_service_info **service_list, int *num_of_services)
{
int result = -1;
struct timeval old_time, cur_time;
int try_count;
int domain;
char service_type_str[128];
if (discover_time_msec <= 0 || discover_time_msec > MAX_SERVICE_DISCOVERY_TIME_MS) {
log_message(LOG_ERR, "discover_time_msec (%d) is invalid. (valid range : 0 < x <= %d)\n", discover_time_msec, MAX_SERVICE_DISCOVERY_TIME_MS);
goto out;
}
domain = check_mdns_domain(service_type);
#ifndef MDNS_NO_RESPONDER_SUPPORT
if (g_svr == NULL) {
log_message(LOG_ERR, "mdnsd is not running.\n");
goto out;
}
if (domain == MDNS_DOMAIN_UNKNOWN) {
switch (g_svr->domain) {
case MDNS_DOMAIN_LOCAL:
domain = MDNS_DOMAIN_LOCAL;
snprintf(service_type_str, sizeof(service_type_str), "%s%s", service_type, MDNS_SUFFIX_LOCAL);
break;
#if defined(CONFIG_NETUTILS_MDNS_XMDNS)
case MDNS_DOMAIN_SITE:
domain = MDNS_DOMAIN_SITE;
snprintf(service_type_str, sizeof(service_type_str), "%s%s", service_type, MDNS_SUFFIX_SITE);
break;
#endif
default:
log_message(LOG_ERR, "current mdns domain is invalid.\n");
goto out;
}
} else {
snprintf(service_type_str, sizeof(service_type_str), "%s", service_type);
}
#else
if (domain == MDNS_DOMAIN_UNKNOWN) {
domain = MDNS_DOMAIN_LOCAL;
snprintf(service_type_str, sizeof(service_type_str), "%s%s", service_type, MDNS_SUFFIX_LOCAL);
} else {
snprintf(service_type_str, sizeof(service_type_str), "%s", service_type);
}
#endif
mdns_cmd_mutex_lock();
#ifndef MDNS_NO_RESPONDER_SUPPORT
if (g_svr->stop_flag == 2) {
/* mdnsd main_loop has terminated by itself */
log_message(LOG_ERR, "main_loop has terminated. mdnsd will stop.\n");
release_mdns_context_resource();
goto out_with_mutex;
}
#else
if (init_mdns_context(domain) != 0) {
log_message(LOG_ERR, "init_mdns_context() failed.\n");
goto out_with_mutex;
}
#endif
pthread_mutex_lock(&g_svr->data_lock);
g_svr->c_status = CACHE_SERVICE_DISCOVERY;
if (g_svr->c_filter) {
MDNS_FREE(g_svr->c_filter);
g_svr->c_filter = NULL;
}
g_svr->c_filter = MDNS_STRDUP(service_type_str);
pthread_mutex_unlock(&g_svr->data_lock);
/* query PTR for service discovery */
struct rr_entry *ptr_e = NULL;
try_count = 0;
TIME_GET(old_time);
do {
if (try_count < MDNS_HOSTNAME_RESOLVER_MAX_TRY_COUNT) {
ptr_e = qn_create(create_nlabel(service_type_str), RR_PTR, 0);
pthread_mutex_lock(&g_svr->data_lock);
rr_list_append(&g_svr->query, ptr_e);
pthread_mutex_unlock(&g_svr->data_lock);
request_sendmsg(g_svr);
try_count++;
}
usleep(MDNS_SERVICE_DISCOVERY_WAIT_TIME_MSEC * 1000);
} while ((TIME_GET(cur_time),TIME_DIFF_USEC(old_time, cur_time)) <= (discover_time_msec * 1000));
if (lookup_service(g_svr, service_type_str, g_service_list, num_of_services) == 0) {
*service_list = g_service_list;
result = 0;
} else {
*service_list = NULL;
}
pthread_mutex_lock(&g_svr->data_lock);
if (g_svr->c_filter) {
MDNS_FREE(g_svr->c_filter);
g_svr->c_filter = NULL;
}
#ifndef MDNS_NO_RESPONDER_SUPPORT
g_svr->c_status = CACHE_NORMAL;
#else
g_svr->c_status = CACHE_SLEEP;
#endif
pthread_mutex_unlock(&g_svr->data_lock);
#ifdef MDNS_NO_RESPONDER_SUPPORT
if (destroy_mdns_context() != 0) {
log_message(LOG_ERR, "destroy_mdns_context() failed.\n");
}
#endif
out_with_mutex:
mdns_cmd_mutex_unlock();
out:
return result;
}