I was searching for a possibility to query SRV DNS records in C/C++. As always, there are high-level library functions (ldns, libunbound), but I didn’t want to introduce another dependency. So I’m using the  many years old functions originally provided by BIND (now included in glibc). The man pages aren’t that helpful as they are incomplete. I found most information here and by debugging.

This was quite a bit work, so I would like to share it with you here. 🙂 You can compile this example with:

$ gcc srvresolv.c -lresolv

srvresolv.c

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
#include <arpa/inet.h>
#include <arpa/nameser.h>
#include <netdb.h>
#include <resolv.h>
 
typedef struct SRVRecord {
	u_int16_t priority;
	u_int16_t weight;
	u_int16_t port;
	char dname[MAXCDNAME];
} SRVRecord;
 
int resolveSRV(const char* host, SRVRecord* resolved) {
	struct __res_state res;
	if (res_ninit(&res) != 0)
		return -1;
 
	unsigned char answer[PACKETSZ];
	int len = res_nsearch(&res, host, C_IN, T_SRV, answer, sizeof(answer));
 
	if (len < 0) {
		fprintf(stderr, "res_nsearch: %s\n", hstrerror(h_errno));
		return -1;
	}
 
	ns_msg handle;
	ns_rr rr;
 
	ns_initparse(answer, len, &handle);
 
	for (int i = 0; i < ns_msg_count(handle, ns_s_an); i++) {
		if (ns_parserr(&handle, ns_s_an, i, &rr) < 0 || ns_rr_type(rr) != T_SRV) {
			perror("ns_parserr");
			continue;
		}
 
		resolved->priority = ns_get16(ns_rr_rdata(rr));
		resolved->weight   = ns_get16(ns_rr_rdata(rr) + NS_INT16SZ);
		resolved->port     = ns_get16(ns_rr_rdata(rr) + 2 * NS_INT16SZ);
		// decompress domain name
		if (dn_expand(ns_msg_base(handle), ns_msg_end(handle), ns_rr_rdata(rr) + 3 * NS_INT16SZ, resolved->dname,
		              sizeof(resolved->dname)) < 0)
			continue;
		return 0;
	}
 
	return -1;
}
 
int main(int argc, char* argv[]) {
	if (argc != 2)
		return 1;
 
	SRVRecord srvrr;
	if (resolveSRV(argv[1], &srvrr))
		fputs("Failed or not found.\n", stderr);
	else
		printf(
		    "Successfully resolved: %s\n"
		    "priority: %d\n"
		    "weight: %d\n"
		    "port: %d\n"
		    "domain name: %s\n",
		    argv[1], srvrr.priority, srvrr.weight, srvrr.port, srvrr.dname);
 
	return 0;
}