Working on libcldc's functionality requires moving all cldc_host
searching and manipulation out from chunkd/tabled and into libcldc
proper. Part of this involves changing the integration points related
to SRV record lookups, so as to accomodate input from a file during
testing (thus avoiding the need for a DNS server w/ SRV records during
testsuite runs).
While working on that, I updated a lot of zaitcev's code in
cld/lib/cldc-dns.c into a libc-like function called getsrvinfo().
getsrvinfo() is intended as an analogue to getaddrinfo(3), for services
based on SRV records (ours, many LDAP/Active Directory services, several
VoIP/SIP services, exim and SMTP, ...).
Maybe this will stay within Project Hail, maybe it will move into POSIX
in ten years or so. But it was a fun mini-project within the scope of
my libcldc work, so I am posting it here, even if hail-devel is perhaps
not the best forum.
Jeff
#ifndef __GETSRVINFO_H__
#define __GETSRVINFO_H__
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
struct srvinfo;
enum srvinfo_error_codes {
ESI_NONE = 0, /* no error */
ESI_CORRUPT = 1, /* server returned bad data */
ESI_FAIL = 2, /* server returned permanent failure */
ESI_AGAIN = 3, /* server returned temporary failure;
* try again later.*/
ESI_OOM = 4, /* internal memory alloc failed */
ESI_INVAL = 5, /* invalid argument(s) */
};
enum srvinfo_flags {
FSI_NO_ADDR = (1U << 0), /* skip host->addrs lookup */
};
struct srvinfo {
unsigned int si_prio; /* SRV priority */
unsigned int si_weight; /* SRV weight */
char *si_target; /* SRV target domainname */
unsigned short si_port; /* SRV port */
struct addrinfo *si_addr; /* addresses returned
* from getaddrinfo(3) lookup
* on si_target */
struct srvinfo *si_next; /* next srvinfo in result list */
};
extern int getsrvinfo(const char *service_name, const char *domain_name,
const struct addrinfo *hints,
unsigned int flags, struct srvinfo **res);
extern void freesrvinfo(struct srvinfo *res);
extern const char *gsi_strerror(int errcode);
#endif /* __GETSRVINFO_H__ */
/*
* getsrvinfo.c
*
* Copyright 2009 Red Hat, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/nameser.h>
#include <netdb.h>
#include <resolv.h>
#include "getsrvinfo.h"
#define __must_be_array(a) \
(__builtin_types_compatible_p(typeof(a), typeof(&a[0])))
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]) + __must_be_array(arr))
enum {
gsi_max_dom_name_sz = 64,
gsi_dns_buf_sz = 1024,
};
static int fill_srvinfo(struct srvinfo *si,
const struct addrinfo *hints,
unsigned int gsi_flags,
unsigned int priority,
unsigned int weight, unsigned int port,
unsigned int nlen, const char *name)
{
char portstr[11];
char *hostname;
struct addrinfo *res0 = NULL;
int rc = 0;
sprintf(portstr, "%u", port);
hostname = malloc(nlen + 1);
if (!hostname) {
rc = -ENOMEM;
goto err_name;
}
memcpy(hostname, name, nlen);
hostname[nlen] = 0;
if (!(gsi_flags & FSI_NO_ADDR)) {
rc = getaddrinfo(hostname, portstr, hints, &res0);
if (rc) {
rc = -EINVAL;
goto err_addr;
}
}
si->si_prio = priority;
si->si_weight = weight;
si->si_target = hostname;
si->si_port = port;
si->si_addr = res0;
si->si_next = NULL;
return 0;
err_addr:
free(hostname);
err_name:
return rc;
}
void freesrvinfo(struct srvinfo *res)
{
while (res) {
struct srvinfo *tmp;
tmp = res;
res = res->si_next;
if (tmp->si_addr)
freeaddrinfo(tmp->si_addr);
free(tmp->si_target);
free(tmp);
}
}
int getsrvinfo(const char *service_name, const char *domain_name,
const struct addrinfo *hints, unsigned int gsi_flags,
struct srvinfo **res_out)
{
unsigned char resp[gsi_dns_buf_sz];
int rlen;
ns_msg nsb;
ns_rr rrb;
int rrlen;
char hostb[gsi_max_dom_name_sz];
struct srvinfo *si, *res = NULL, *res_last = NULL;
const unsigned char *p;
int rc, gsi_rc, i;
if (!domain_name || !res_out)
return ESI_INVAL;
*res_out = NULL;
/* we concatencate service_name and domain_name as a helpful
* service for the caller, because it is very common
* that service_name is either completely static, or at least
* stored in a separate variable from domain_name.
*/
if (service_name) {
char *name;
size_t name_len;
int has_dot;
has_dot = (service_name[strlen(service_name) - 1] == '.');
name_len = strlen(service_name) + strlen(domain_name) +
(has_dot ? 0 : 1) + 1;
name = malloc(name_len);
if (!name)
return ESI_OOM;
snprintf(name, name_len, "%s%s%s", service_name,
has_dot ? "" : ".",
domain_name);
rc = res_search(name, ns_c_in, ns_t_srv, resp, sizeof(resp));
free(name);
} else {
rc = res_search(domain_name, ns_c_in, ns_t_srv,
resp, sizeof(resp));
}
/* parse resolver return value */
if (rc < 0) {
switch (h_errno) {
case TRY_AGAIN:
return ESI_AGAIN;
case HOST_NOT_FOUND:
case NO_DATA:
case NO_RECOVERY:
default:
return ESI_FAIL;
}
}
rlen = rc;
if (rlen == 0)
return ESI_FAIL;
/* set up DNS result parse */
if (ns_initparse(resp, rlen, &nsb) < 0)
return ESI_CORRUPT;
/* iterate through each answer. Because DNS packets may
* be truncated, we do not signal an error on
* short-length faults found during packet parsing
*/
for (i = 0; i < ns_msg_count(nsb, ns_s_an); i++) {
rc = ns_parserr(&nsb, ns_s_an, i, &rrb);
if (rc < 0)
continue;
if (ns_rr_class(rrb) != ns_c_in)
continue;
switch (ns_rr_type(rrb)) {
case ns_t_srv:
rrlen = ns_rr_rdlen(rrb);
if (rrlen < 8) { /* 2+2+2 and 2 for host */
break;
}
p = ns_rr_rdata(rrb);
rc = dn_expand(resp, resp+rlen, p+6,
hostb, gsi_max_dom_name_sz);
if (rc < 0) {
break;
}
if (rc < 2) {
break;
}
si = malloc(sizeof(*si));
if (!si) {
gsi_rc = ESI_OOM;
goto err_out;
}
if (fill_srvinfo(si, hints, gsi_flags,
ns_get16(p+0),
ns_get16(p+2),
ns_get16(p+4),
rc, hostb)) {
free(si);
gsi_rc = ESI_OOM;
goto err_out;
}
/* if first item, set BOL-ptr */
if (!res)
res = si;
/* append to EOL */
if (res_last)
res_last->si_next = si;
res_last = si;
break;
case ns_t_cname: /* impossible, but ... ? */
default:
break;
}
}
*res_out = res;
return ESI_NONE;
err_out:
freesrvinfo(res);
return gsi_rc;
}
static const char *gsi_error_str[] = {
[ESI_NONE] = "no error",
[ESI_CORRUPT] = "server returned bad data",
[ESI_FAIL] = "server returned permanent failure",
[ESI_AGAIN] = "server returned temporary failure",
[ESI_OOM] = "internal memory alloc failed",
[ESI_INVAL] = "invalid argument(s)",
};
const char *gsi_strerror(int errcode)
{
if (errcode < 0 || errcode >= ARRAY_SIZE(gsi_error_str))
return NULL;
return gsi_error_str[errcode];
}
--
To unsubscribe from this list: send the line "unsubscribe hail-devel" in
the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html