add mDNS lookup method to libosmo-mslookup (#2)

Add the first actually useful lookup method to the mslookup library: multicast
DNS.

The server side is added in a subsequent commit, when the mslookup server is
implemented for the osmo-hlr program.

Use custom DNS encoding instead of libc-ares (which we use in OsmoSGSN
already), because libc-ares is only a DNS client implementation and we will
need both client and server.

Resubmit of f10463c5fc6d9e786ab7c648d99f7450f9a25906 after being
reverted in 110a49f69f29fed844d8743b76fd748f4a14812a. This new version
skips the mslookup_client_mdns test if multicast is not supported in the
build environment. I have verified that it doesn't break the build
anymore in my own OBS namespace.

Related: OS#4237, OS#4361
Patch-by: osmith, nhofmeyr
Change-Id: I3c340627181b632dd6a0d577aa2ea2a7cd035c0c
diff --git a/src/mslookup/mdns_rfc.c b/src/mslookup/mdns_rfc.c
new file mode 100644
index 0000000..e1fc184
--- /dev/null
+++ b/src/mslookup/mdns_rfc.c
@@ -0,0 +1,265 @@
+/* Low level mDNS encoding and decoding functions of the qname IE, header/question sections and resource records,
+ * as described in these RFCs:
+ * - RFC 1035 (Domain names - implementation and specification)
+ * - RFC 3596 (DNS Extensions to Support IP Version 6) */
+
+/* Copyright 2019 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/bitvec.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/mslookup/mdns_rfc.h>
+
+/*
+ * Encode/decode IEs
+ */
+
+/*! Encode a domain string as qname (RFC 1035 4.1.2).
+ * \param[in] domain  multiple labels separated by dots, e.g. "sip.voice.1234.msisdn".
+ * \returns allocated buffer with length-value pairs for each label (e.g. 0x03 "sip" 0x05 "voice" ...), NULL on error.
+ */
+char *osmo_mdns_rfc_qname_encode(void *ctx, const char *domain)
+{
+	char *domain_dup;
+	char *domain_iter;
+	char buf[OSMO_MDNS_RFC_MAX_NAME_LEN + 2] = ""; /* len(qname) is len(domain) +1 */
+	struct osmo_strbuf sb = { .buf = buf, .len = sizeof(buf) };
+	char *label;
+
+	if (strlen(domain) > OSMO_MDNS_RFC_MAX_NAME_LEN)
+		return NULL;
+
+	domain_iter = domain_dup = talloc_strdup(ctx, domain);
+	while ((label = strsep(&domain_iter, "."))) {
+		size_t len = strlen(label);
+
+		/* Empty domain, dot at start, two dots in a row, or ending with a dot */
+		if (!len)
+			goto error;
+
+		OSMO_STRBUF_PRINTF(sb, "%c%s", (char)len, label);
+	}
+
+	talloc_free(domain_dup);
+	return talloc_strdup(ctx, buf);
+
+error:
+	talloc_free(domain_dup);
+	return NULL;
+}
+
+/*! Decode a domain string from a qname (RFC 1035 4.1.2).
+ * \param[in] qname  buffer with length-value pairs for each label (e.g. 0x03 "sip" 0x05 "voice" ...)
+ * \param[in] qname_max_len  amount of bytes that can be read at most from the memory location that qname points to.
+ * \returns allocated buffer with domain string, multiple labels separated by dots (e.g. "sip.voice.1234.msisdn"),
+ *	    NULL on error.
+ */
+char *osmo_mdns_rfc_qname_decode(void *ctx, const char *qname, size_t qname_max_len)
+{
+	const char *next_label, *qname_end = qname + qname_max_len;
+	char buf[OSMO_MDNS_RFC_MAX_NAME_LEN + 1];
+	int i = 0;
+
+	if (qname_max_len < 1)
+		return NULL;
+
+	while (*qname) {
+		size_t len = *qname;
+		next_label = qname + len + 1;
+
+		if (next_label >= qname_end || i + len > OSMO_MDNS_RFC_MAX_NAME_LEN)
+			return NULL;
+
+		if (i) {
+			/* Two dots in a row is not allowed */
+			if (buf[i - 1] == '.')
+				return NULL;
+
+			buf[i] = '.';
+			i++;
+		}
+
+		memcpy(buf + i, qname + 1, len);
+		i += len;
+		qname = next_label;
+	}
+	buf[i] = '\0';
+
+	return talloc_strdup(ctx, buf);
+}
+
+/*
+ * Encode/decode message sections
+ */
+
+/*! Encode header section (RFC 1035 4.1.1).
+ * \param[in] msgb  mesage buffer to which the encoded data will be appended.
+ */
+void osmo_mdns_rfc_header_encode(struct msgb *msg, const struct osmo_mdns_rfc_header *hdr)
+{
+	struct osmo_mdns_rfc_header *buf = (struct osmo_mdns_rfc_header *) msgb_put(msg, sizeof(*hdr));
+	memcpy(buf, hdr, sizeof(*hdr));
+
+	osmo_store16be(buf->id, &buf->id);
+	osmo_store16be(buf->qdcount, &buf->qdcount);
+	osmo_store16be(buf->ancount, &buf->ancount);
+	osmo_store16be(buf->nscount, &buf->nscount);
+	osmo_store16be(buf->arcount, &buf->arcount);
+}
+
+/*! Decode header section (RFC 1035 4.1.1). */
+int osmo_mdns_rfc_header_decode(const uint8_t *data, size_t data_len, struct osmo_mdns_rfc_header *hdr)
+{
+	if (data_len != sizeof(*hdr))
+		return -EINVAL;
+
+	memcpy(hdr, data, data_len);
+
+	hdr->id = osmo_load16be(&hdr->id);
+	hdr->qdcount = osmo_load16be(&hdr->qdcount);
+	hdr->ancount = osmo_load16be(&hdr->ancount);
+	hdr->nscount = osmo_load16be(&hdr->nscount);
+	hdr->arcount = osmo_load16be(&hdr->arcount);
+
+	return 0;
+}
+
+/*! Encode question section (RFC 1035 4.1.2).
+ * \param[in] msgb  mesage buffer to which the encoded data will be appended.
+ */
+int osmo_mdns_rfc_question_encode(void *ctx, struct msgb *msg, const struct osmo_mdns_rfc_question *qst)
+{
+	char *qname;
+	size_t qname_len;
+	uint8_t *qname_buf;
+
+	/* qname */
+	qname = osmo_mdns_rfc_qname_encode(ctx, qst->domain);
+	if (!qname)
+		return -EINVAL;
+	qname_len = strlen(qname) + 1;
+	qname_buf = msgb_put(msg, qname_len);
+	memcpy(qname_buf, qname, qname_len);
+	talloc_free(qname);
+
+	/* qtype and qclass */
+	msgb_put_u16(msg, qst->qtype);
+	msgb_put_u16(msg, qst->qclass);
+
+	return 0;
+}
+
+/*! Decode question section (RFC 1035 4.1.2). */
+struct osmo_mdns_rfc_question *osmo_mdns_rfc_question_decode(void *ctx, const uint8_t *data, size_t data_len)
+{
+	struct osmo_mdns_rfc_question *ret;
+	size_t qname_len = data_len - 4;
+
+	if (data_len < 6)
+		return NULL;
+
+	/* qname */
+	ret = talloc_zero(ctx, struct osmo_mdns_rfc_question);
+	if (!ret)
+		return NULL;
+	ret->domain = osmo_mdns_rfc_qname_decode(ret, (const char *)data, qname_len);
+	if (!ret->domain) {
+		talloc_free(ret);
+		return NULL;
+	}
+
+	/* qtype and qclass */
+	ret->qtype = osmo_load16be(data + qname_len);
+	ret->qclass = osmo_load16be(data + qname_len + 2);
+
+	return ret;
+}
+
+/*
+ * Encode/decode resource records
+ */
+
+/*! Encode one resource record (RFC 1035 4.1.3).
+ * \param[in] msgb  mesage buffer to which the encoded data will be appended.
+ */
+int osmo_mdns_rfc_record_encode(void *ctx, struct msgb *msg, const struct osmo_mdns_rfc_record *rec)
+{
+	char *name;
+	size_t name_len;
+	uint8_t *buf;
+
+	/* name */
+	name = osmo_mdns_rfc_qname_encode(ctx, rec->domain);
+	if (!name)
+		return -EINVAL;
+	name_len = strlen(name) + 1;
+	buf = msgb_put(msg, name_len);
+	memcpy(buf, name, name_len);
+	talloc_free(name);
+
+	/* type, class, ttl, rdlength */
+	msgb_put_u16(msg, rec->type);
+	msgb_put_u16(msg, rec->class);
+	msgb_put_u32(msg, rec->ttl);
+	msgb_put_u16(msg, rec->rdlength);
+
+	/* rdata */
+	buf = msgb_put(msg, rec->rdlength);
+	memcpy(buf, rec->rdata, rec->rdlength);
+	return 0;
+}
+
+/*! Decode one resource record (RFC 1035 4.1.3). */
+struct osmo_mdns_rfc_record *osmo_mdns_rfc_record_decode(void *ctx, const uint8_t *data, size_t data_len,
+						       size_t *record_len)
+{
+	struct osmo_mdns_rfc_record *ret = talloc_zero(ctx, struct osmo_mdns_rfc_record);
+	size_t name_len;
+
+	/* name */
+	ret->domain = osmo_mdns_rfc_qname_decode(ret, (const char *)data, data_len - 10);
+	if (!ret->domain)
+		goto error;
+	name_len = strlen(ret->domain) + 2;
+	if (name_len + 10 > data_len)
+		goto error;
+
+	/* type, class, ttl, rdlength */
+	ret->type = osmo_load16be(data + name_len);
+	ret->class = osmo_load16be(data + name_len + 2);
+	ret->ttl = osmo_load32be(data + name_len + 4);
+	ret->rdlength = osmo_load16be(data + name_len + 8);
+	if (name_len + 10 + ret->rdlength > data_len)
+		goto error;
+
+	/* rdata */
+	ret->rdata = talloc_memdup(ret, data + name_len + 10, ret->rdlength);
+	if (!ret->rdata)
+		return NULL;
+
+	*record_len = name_len + 10 + ret->rdlength;
+	return ret;
+error:
+	talloc_free(ret);
+	return NULL;
+}
+