aboutsummaryrefslogtreecommitdiffstats
path: root/src/mslookup/mdns_msg.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/mslookup/mdns_msg.c')
-rw-r--r--src/mslookup/mdns_msg.c261
1 files changed, 261 insertions, 0 deletions
diff --git a/src/mslookup/mdns_msg.c b/src/mslookup/mdns_msg.c
new file mode 100644
index 0000000..da65fef
--- /dev/null
+++ b/src/mslookup/mdns_msg.c
@@ -0,0 +1,261 @@
+/* High level mDNS encoding and decoding functions for whole messages:
+ * Request message (header, question)
+ * Answer message (header, resource record 1, ... resource record N)*/
+
+/* 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 <errno.h>
+#include <string.h>
+#include <osmocom/hlr/logging.h>
+#include <osmocom/mslookup/mdns_msg.h>
+
+/*! Encode request message into one mDNS packet, consisting of the header section and one question section.
+ * \returns 0 on success, -EINVAL on error.
+ */
+int osmo_mdns_msg_request_encode(void *ctx, struct msgb *msg, const struct osmo_mdns_msg_request *req)
+{
+ struct osmo_mdns_rfc_header hdr = {0};
+ struct osmo_mdns_rfc_question qst = {0};
+
+ hdr.id = req->id;
+ hdr.qdcount = 1;
+ osmo_mdns_rfc_header_encode(msg, &hdr);
+
+ qst.domain = req->domain;
+ qst.qtype = req->type;
+ qst.qclass = OSMO_MDNS_RFC_CLASS_IN;
+ if (osmo_mdns_rfc_question_encode(ctx, msg, &qst) != 0)
+ return -EINVAL;
+
+ return 0;
+}
+
+/*! Decode request message from a mDNS packet, consisting of the header section and one question section.
+ * \returns allocated request message on success, NULL on error.
+ */
+struct osmo_mdns_msg_request *osmo_mdns_msg_request_decode(void *ctx, const uint8_t *data, size_t data_len)
+{
+ struct osmo_mdns_rfc_header hdr = {0};
+ size_t hdr_len = sizeof(struct osmo_mdns_rfc_header);
+ struct osmo_mdns_rfc_question* qst = NULL;
+ struct osmo_mdns_msg_request *ret = NULL;
+
+ if (data_len < hdr_len || osmo_mdns_rfc_header_decode(data, hdr_len, &hdr) != 0 || hdr.qr != 0)
+ return NULL;
+
+ qst = osmo_mdns_rfc_question_decode(ctx, data + hdr_len, data_len - hdr_len);
+ if (!qst)
+ return NULL;
+
+ ret = talloc_zero(ctx, struct osmo_mdns_msg_request);
+ ret->id = hdr.id;
+ ret->domain = talloc_strdup(ret, qst->domain);
+ ret->type = qst->qtype;
+
+ talloc_free(qst);
+ return ret;
+}
+
+/*! Initialize the linked list for resource records in a answer message. */
+void osmo_mdns_msg_answer_init(struct osmo_mdns_msg_answer *ans)
+{
+ *ans = (struct osmo_mdns_msg_answer){};
+ INIT_LLIST_HEAD(&ans->records);
+}
+
+/*! Encode answer message into one mDNS packet, consisting of the header section and N resource records.
+ *
+ * To keep things simple, this sends the domain with each resource record. Other DNS implementations make use of
+ * "message compression", which would send a question section with the domain before the resource records, and then
+ * point inside each resource record with an offset back to the domain in the question section (RFC 1035 4.1.4).
+ * \returns 0 on success, -EINVAL on error.
+ */
+int osmo_mdns_msg_answer_encode(void *ctx, struct msgb *msg, const struct osmo_mdns_msg_answer *ans)
+{
+ struct osmo_mdns_rfc_header hdr = {0};
+ struct osmo_mdns_record *ans_record;
+
+ hdr.id = ans->id;
+ hdr.qr = 1;
+ hdr.ancount = llist_count(&ans->records);
+ osmo_mdns_rfc_header_encode(msg, &hdr);
+
+ llist_for_each_entry(ans_record, &ans->records, list) {
+ struct osmo_mdns_rfc_record rec = {0};
+
+ rec.domain = ans->domain;
+ rec.type = ans_record->type;
+ rec.class = OSMO_MDNS_RFC_CLASS_IN;
+ rec.ttl = 0;
+ rec.rdlength = ans_record->length;
+ rec.rdata = ans_record->data;
+
+ if (osmo_mdns_rfc_record_encode(ctx, msg, &rec) != 0)
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*! Decode answer message from a mDNS packet.
+ *
+ * Answer messages must consist of one header and one or more resource records. An additional question section or
+ * message compression (RFC 1035 4.1.4) are not supported.
+* \returns allocated answer message on success, NULL on error.
+ */
+struct osmo_mdns_msg_answer *osmo_mdns_msg_answer_decode(void *ctx, const uint8_t *data, size_t data_len)
+{
+ struct osmo_mdns_rfc_header hdr = {};
+ size_t hdr_len = sizeof(struct osmo_mdns_rfc_header);
+ struct osmo_mdns_msg_answer *ret = talloc_zero(ctx, struct osmo_mdns_msg_answer);
+
+ /* Parse header section */
+ if (data_len < hdr_len || osmo_mdns_rfc_header_decode(data, hdr_len, &hdr) != 0 || hdr.qr != 1)
+ goto error;
+ ret->id = hdr.id;
+ data_len -= hdr_len;
+ data += hdr_len;
+
+ /* Parse resource records */
+ INIT_LLIST_HEAD(&ret->records);
+ while (data_len) {
+ size_t record_len;
+ struct osmo_mdns_rfc_record *rec;
+ struct osmo_mdns_record* ret_record;
+
+ rec = osmo_mdns_rfc_record_decode(ret, data, data_len, &record_len);
+ if (!rec)
+ goto error;
+
+ /* Copy domain to ret */
+ if (ret->domain) {
+ if (strcmp(ret->domain, rec->domain) != 0) {
+ LOGP(DMSLOOKUP, LOGL_ERROR, "domain mismatch in resource records ('%s' vs '%s')\n",
+ ret->domain, rec->domain);
+ goto error;
+ }
+ }
+ else
+ ret->domain = talloc_strdup(ret, rec->domain);
+
+ /* Add simplified record to ret */
+ ret_record = talloc_zero(ret, struct osmo_mdns_record);
+ ret_record->type = rec->type;
+ ret_record->length = rec->rdlength;
+ ret_record->data = talloc_memdup(ret_record, rec->rdata, rec->rdlength);
+ llist_add_tail(&ret_record->list, &ret->records);
+
+ data += record_len;
+ data_len -= record_len;
+ talloc_free(rec);
+ }
+
+ /* Verify record count */
+ if (llist_count(&ret->records) != hdr.ancount) {
+ LOGP(DMSLOOKUP, LOGL_ERROR, "amount of parsed records (%i) doesn't match count in header (%i)\n",
+ llist_count(&ret->records), hdr.ancount);
+ goto error;
+ }
+
+ return ret;
+error:
+ talloc_free(ret);
+ return NULL;
+}
+
+/*! Get a TXT resource record, which stores a key=value string.
+ * \returns allocated resource record on success, NULL on error.
+ */
+static struct osmo_mdns_record *_osmo_mdns_record_txt_encode(void *ctx, const char *key, const char *value)
+{
+ struct osmo_mdns_record *ret = talloc_zero(ctx, struct osmo_mdns_record);
+ size_t len = strlen(key) + 1 + strlen(value);
+
+ if (len > OSMO_MDNS_RFC_MAX_CHARACTER_STRING_LEN - 1)
+ return NULL;
+
+ /* redundant len is required, see RFC 1035 3.3.14 and 3.3. */
+ ret->data = (uint8_t *)talloc_asprintf(ctx, "%c%s=%s", (char)len, key, value);
+ if (!ret->data)
+ return NULL;
+ ret->type = OSMO_MDNS_RFC_RECORD_TYPE_TXT;
+ ret->length = len + 1;
+ return ret;
+}
+
+/*! Get a TXT resource record, which stores a key=value string, but build value from a format string.
+ * \returns allocated resource record on success, NULL on error.
+ */
+struct osmo_mdns_record *osmo_mdns_record_txt_keyval_encode(void *ctx, const char *key, const char *value_fmt, ...)
+{
+ va_list ap;
+ char *value = NULL;
+ struct osmo_mdns_record *r;
+
+ if (!value_fmt)
+ return _osmo_mdns_record_txt_encode(ctx, key, "");
+
+ va_start(ap, value_fmt);
+ value = talloc_vasprintf(ctx, value_fmt, ap);
+ if (!value)
+ return NULL;
+ va_end(ap);
+ r = _osmo_mdns_record_txt_encode(ctx, key, value);
+ talloc_free(value);
+ return r;
+}
+
+/*! Decode a TXT resource record, which stores a key=value string.
+ * \returns 0 on success, -EINVAL on error.
+ */
+int osmo_mdns_record_txt_keyval_decode(const struct osmo_mdns_record *rec,
+ char *key_buf, size_t key_size, char *value_buf, size_t value_size)
+{
+ const char *key_value;
+ const char *key_value_end;
+ const char *sep;
+ const char *value;
+
+ if (rec->type != OSMO_MDNS_RFC_RECORD_TYPE_TXT)
+ return -EINVAL;
+
+ key_value = (const char *)rec->data;
+ key_value_end = key_value + rec->length;
+
+ /* Verify and then skip the redundant string length byte */
+ if (*key_value != rec->length - 1)
+ return -EINVAL;
+ key_value++;
+
+ if (key_value >= key_value_end)
+ return -EINVAL;
+
+ /* Find equals sign */
+ sep = osmo_strnchr(key_value, key_value_end - key_value, '=');
+ if (!sep)
+ return -EINVAL;
+
+ /* Parse key */
+ osmo_print_n(key_buf, key_size, key_value, sep - key_value);
+
+ /* Parse value */
+ value = sep + 1;
+ osmo_print_n(value_buf, value_size, value, key_value_end - value);
+ return 0;
+}