blob: 6f39a8e7747cd8a5ed82cfa851fd1f33c99417c8 [file] [log] [blame]
Oliver Smith3a9f2672019-11-20 10:56:35 +01001/* Low level mDNS encoding and decoding functions of the qname IE, header/question sections and resource records,
2 * as described in these RFCs:
3 * - RFC 1035 (Domain names - implementation and specification)
4 * - RFC 3596 (DNS Extensions to Support IP Version 6) */
5
6/* Copyright 2019 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
7 *
8 * All Rights Reserved
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License along
21 * with this program. If not, see <http://www.gnu.org/licenses/>.
22 */
23
24#include <string.h>
25#include <ctype.h>
26#include <errno.h>
27#include <osmocom/core/msgb.h>
28#include <osmocom/core/bitvec.h>
29#include <osmocom/core/logging.h>
30#include <osmocom/mslookup/mdns_rfc.h>
31
32/*
33 * Encode/decode IEs
34 */
35
36/*! Encode a domain string as qname (RFC 1035 4.1.2).
37 * \param[in] domain multiple labels separated by dots, e.g. "sip.voice.1234.msisdn".
38 * \returns allocated buffer with length-value pairs for each label (e.g. 0x03 "sip" 0x05 "voice" ...), NULL on error.
39 */
40char *osmo_mdns_rfc_qname_encode(void *ctx, const char *domain)
41{
42 char *domain_dup;
43 char *domain_iter;
44 char buf[OSMO_MDNS_RFC_MAX_NAME_LEN + 2] = ""; /* len(qname) is len(domain) +1 */
45 struct osmo_strbuf sb = { .buf = buf, .len = sizeof(buf) };
46 char *label;
47
48 if (strlen(domain) > OSMO_MDNS_RFC_MAX_NAME_LEN)
49 return NULL;
50
51 domain_iter = domain_dup = talloc_strdup(ctx, domain);
52 while ((label = strsep(&domain_iter, "."))) {
53 size_t len = strlen(label);
54
55 /* Empty domain, dot at start, two dots in a row, or ending with a dot */
56 if (!len)
57 goto error;
58
59 OSMO_STRBUF_PRINTF(sb, "%c%s", (char)len, label);
60 }
61
62 talloc_free(domain_dup);
63 return talloc_strdup(ctx, buf);
64
65error:
66 talloc_free(domain_dup);
67 return NULL;
68}
69
70/*! Decode a domain string from a qname (RFC 1035 4.1.2).
71 * \param[in] qname buffer with length-value pairs for each label (e.g. 0x03 "sip" 0x05 "voice" ...)
72 * \param[in] qname_max_len amount of bytes that can be read at most from the memory location that qname points to.
73 * \returns allocated buffer with domain string, multiple labels separated by dots (e.g. "sip.voice.1234.msisdn"),
74 * NULL on error.
75 */
76char *osmo_mdns_rfc_qname_decode(void *ctx, const char *qname, size_t qname_max_len)
77{
78 const char *next_label, *qname_end = qname + qname_max_len;
79 char buf[OSMO_MDNS_RFC_MAX_NAME_LEN + 1];
80 int i = 0;
81
82 if (qname_max_len < 1)
83 return NULL;
84
85 while (*qname) {
Oliver Smith74e70722020-01-13 15:51:56 +010086 size_t len;
87
88 if (i >= qname_max_len)
89 return NULL;
90
91 len = *qname;
Oliver Smith3a9f2672019-11-20 10:56:35 +010092 next_label = qname + len + 1;
93
94 if (next_label >= qname_end || i + len > OSMO_MDNS_RFC_MAX_NAME_LEN)
95 return NULL;
96
97 if (i) {
98 /* Two dots in a row is not allowed */
99 if (buf[i - 1] == '.')
100 return NULL;
101
102 buf[i] = '.';
103 i++;
104 }
105
106 memcpy(buf + i, qname + 1, len);
107 i += len;
108 qname = next_label;
109 }
110 buf[i] = '\0';
111
112 return talloc_strdup(ctx, buf);
113}
114
115/*
116 * Encode/decode message sections
117 */
118
119/*! Encode header section (RFC 1035 4.1.1).
120 * \param[in] msgb mesage buffer to which the encoded data will be appended.
121 */
122void osmo_mdns_rfc_header_encode(struct msgb *msg, const struct osmo_mdns_rfc_header *hdr)
123{
124 struct osmo_mdns_rfc_header *buf = (struct osmo_mdns_rfc_header *) msgb_put(msg, sizeof(*hdr));
125 memcpy(buf, hdr, sizeof(*hdr));
126
127 osmo_store16be(buf->id, &buf->id);
128 osmo_store16be(buf->qdcount, &buf->qdcount);
129 osmo_store16be(buf->ancount, &buf->ancount);
130 osmo_store16be(buf->nscount, &buf->nscount);
131 osmo_store16be(buf->arcount, &buf->arcount);
132}
133
134/*! Decode header section (RFC 1035 4.1.1). */
135int osmo_mdns_rfc_header_decode(const uint8_t *data, size_t data_len, struct osmo_mdns_rfc_header *hdr)
136{
137 if (data_len != sizeof(*hdr))
138 return -EINVAL;
139
140 memcpy(hdr, data, data_len);
141
142 hdr->id = osmo_load16be(&hdr->id);
143 hdr->qdcount = osmo_load16be(&hdr->qdcount);
144 hdr->ancount = osmo_load16be(&hdr->ancount);
145 hdr->nscount = osmo_load16be(&hdr->nscount);
146 hdr->arcount = osmo_load16be(&hdr->arcount);
147
148 return 0;
149}
150
151/*! Encode question section (RFC 1035 4.1.2).
152 * \param[in] msgb mesage buffer to which the encoded data will be appended.
153 */
154int osmo_mdns_rfc_question_encode(void *ctx, struct msgb *msg, const struct osmo_mdns_rfc_question *qst)
155{
156 char *qname;
157 size_t qname_len;
158 uint8_t *qname_buf;
159
160 /* qname */
161 qname = osmo_mdns_rfc_qname_encode(ctx, qst->domain);
162 if (!qname)
163 return -EINVAL;
164 qname_len = strlen(qname) + 1;
165 qname_buf = msgb_put(msg, qname_len);
166 memcpy(qname_buf, qname, qname_len);
167 talloc_free(qname);
168
169 /* qtype and qclass */
170 msgb_put_u16(msg, qst->qtype);
171 msgb_put_u16(msg, qst->qclass);
172
173 return 0;
174}
175
176/*! Decode question section (RFC 1035 4.1.2). */
177struct osmo_mdns_rfc_question *osmo_mdns_rfc_question_decode(void *ctx, const uint8_t *data, size_t data_len)
178{
179 struct osmo_mdns_rfc_question *ret;
180 size_t qname_len = data_len - 4;
181
182 if (data_len < 6)
183 return NULL;
184
185 /* qname */
186 ret = talloc_zero(ctx, struct osmo_mdns_rfc_question);
187 if (!ret)
188 return NULL;
189 ret->domain = osmo_mdns_rfc_qname_decode(ret, (const char *)data, qname_len);
190 if (!ret->domain) {
191 talloc_free(ret);
192 return NULL;
193 }
194
195 /* qtype and qclass */
196 ret->qtype = osmo_load16be(data + qname_len);
197 ret->qclass = osmo_load16be(data + qname_len + 2);
198
199 return ret;
200}
201
202/*
203 * Encode/decode resource records
204 */
205
206/*! Encode one resource record (RFC 1035 4.1.3).
207 * \param[in] msgb mesage buffer to which the encoded data will be appended.
208 */
209int osmo_mdns_rfc_record_encode(void *ctx, struct msgb *msg, const struct osmo_mdns_rfc_record *rec)
210{
211 char *name;
212 size_t name_len;
213 uint8_t *buf;
214
215 /* name */
216 name = osmo_mdns_rfc_qname_encode(ctx, rec->domain);
217 if (!name)
218 return -EINVAL;
219 name_len = strlen(name) + 1;
220 buf = msgb_put(msg, name_len);
221 memcpy(buf, name, name_len);
222 talloc_free(name);
223
224 /* type, class, ttl, rdlength */
225 msgb_put_u16(msg, rec->type);
226 msgb_put_u16(msg, rec->class);
227 msgb_put_u32(msg, rec->ttl);
228 msgb_put_u16(msg, rec->rdlength);
229
230 /* rdata */
231 buf = msgb_put(msg, rec->rdlength);
232 memcpy(buf, rec->rdata, rec->rdlength);
233 return 0;
234}
235
236/*! Decode one resource record (RFC 1035 4.1.3). */
237struct osmo_mdns_rfc_record *osmo_mdns_rfc_record_decode(void *ctx, const uint8_t *data, size_t data_len,
238 size_t *record_len)
239{
240 struct osmo_mdns_rfc_record *ret = talloc_zero(ctx, struct osmo_mdns_rfc_record);
241 size_t name_len;
242
243 /* name */
244 ret->domain = osmo_mdns_rfc_qname_decode(ret, (const char *)data, data_len - 10);
245 if (!ret->domain)
246 goto error;
247 name_len = strlen(ret->domain) + 2;
248 if (name_len + 10 > data_len)
249 goto error;
250
251 /* type, class, ttl, rdlength */
252 ret->type = osmo_load16be(data + name_len);
253 ret->class = osmo_load16be(data + name_len + 2);
254 ret->ttl = osmo_load32be(data + name_len + 4);
255 ret->rdlength = osmo_load16be(data + name_len + 8);
256 if (name_len + 10 + ret->rdlength > data_len)
257 goto error;
258
259 /* rdata */
260 ret->rdata = talloc_memdup(ret, data + name_len + 10, ret->rdlength);
261 if (!ret->rdata)
262 return NULL;
263
264 *record_len = name_len + 10 + ret->rdlength;
265 return ret;
266error:
267 talloc_free(ret);
268 return NULL;
269}
270