getsrvinfo(3)

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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 majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Fedora Clound]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [XFree86]

  Powered by Linux