| /* 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; |
| |
| if (i >= qname_max_len) |
| return NULL; |
| |
| 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; |
| } |
| |