blob: 78ea67a065d3074fc5cd76358ae398c7d3b27579 [file] [log] [blame]
Oliver Smith3a9f2672019-11-20 10:56:35 +01001/* High level mDNS encoding and decoding functions for whole messages:
2 * Request message (header, question)
3 * Answer message (header, resource record 1, ... resource record N)*/
4
5/* Copyright 2019 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
6 *
7 * All Rights Reserved
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License along
20 * with this program. If not, see <http://www.gnu.org/licenses/>.
21 */
22
23#include <errno.h>
24#include <string.h>
25#include <osmocom/hlr/logging.h>
26#include <osmocom/mslookup/mdns_msg.h>
27
28/*! Encode request message into one mDNS packet, consisting of the header section and one question section.
29 * \returns 0 on success, -EINVAL on error.
30 */
31int osmo_mdns_msg_request_encode(void *ctx, struct msgb *msg, const struct osmo_mdns_msg_request *req)
32{
33 struct osmo_mdns_rfc_header hdr = {0};
34 struct osmo_mdns_rfc_question qst = {0};
35
36 hdr.id = req->id;
37 hdr.qdcount = 1;
38 osmo_mdns_rfc_header_encode(msg, &hdr);
39
40 qst.domain = req->domain;
41 qst.qtype = req->type;
42 qst.qclass = OSMO_MDNS_RFC_CLASS_IN;
43 if (osmo_mdns_rfc_question_encode(ctx, msg, &qst) != 0)
44 return -EINVAL;
45
46 return 0;
47}
48
49/*! Decode request message from a mDNS packet, consisting of the header section and one question section.
50 * \returns allocated request message on success, NULL on error.
51 */
52struct osmo_mdns_msg_request *osmo_mdns_msg_request_decode(void *ctx, const uint8_t *data, size_t data_len)
53{
54 struct osmo_mdns_rfc_header hdr = {0};
55 size_t hdr_len = sizeof(struct osmo_mdns_rfc_header);
56 struct osmo_mdns_rfc_question* qst = NULL;
57 struct osmo_mdns_msg_request *ret = NULL;
58
59 if (data_len < hdr_len || osmo_mdns_rfc_header_decode(data, hdr_len, &hdr) != 0 || hdr.qr != 0)
60 return NULL;
61
62 qst = osmo_mdns_rfc_question_decode(ctx, data + hdr_len, data_len - hdr_len);
63 if (!qst)
64 return NULL;
65
66 ret = talloc_zero(ctx, struct osmo_mdns_msg_request);
67 ret->id = hdr.id;
68 ret->domain = talloc_strdup(ret, qst->domain);
69 ret->type = qst->qtype;
70
71 talloc_free(qst);
72 return ret;
73}
74
75/*! Initialize the linked list for resource records in a answer message. */
76void osmo_mdns_msg_answer_init(struct osmo_mdns_msg_answer *ans)
77{
78 *ans = (struct osmo_mdns_msg_answer){};
79 INIT_LLIST_HEAD(&ans->records);
80}
81
82/*! Encode answer message into one mDNS packet, consisting of the header section and N resource records.
83 *
84 * To keep things simple, this sends the domain with each resource record. Other DNS implementations make use of
85 * "message compression", which would send a question section with the domain before the resource records, and then
86 * point inside each resource record with an offset back to the domain in the question section (RFC 1035 4.1.4).
87 * \returns 0 on success, -EINVAL on error.
88 */
89int osmo_mdns_msg_answer_encode(void *ctx, struct msgb *msg, const struct osmo_mdns_msg_answer *ans)
90{
91 struct osmo_mdns_rfc_header hdr = {0};
92 struct osmo_mdns_record *ans_record;
93
94 hdr.id = ans->id;
95 hdr.qr = 1;
96 hdr.ancount = llist_count(&ans->records);
97 osmo_mdns_rfc_header_encode(msg, &hdr);
98
99 llist_for_each_entry(ans_record, &ans->records, list) {
100 struct osmo_mdns_rfc_record rec = {0};
101
102 rec.domain = ans->domain;
103 rec.type = ans_record->type;
104 rec.class = OSMO_MDNS_RFC_CLASS_IN;
105 rec.ttl = 0;
106 rec.rdlength = ans_record->length;
107 rec.rdata = ans_record->data;
108
109 if (osmo_mdns_rfc_record_encode(ctx, msg, &rec) != 0)
110 return -EINVAL;
111 }
112
113 return 0;
114}
115
116/*! Decode answer message from a mDNS packet.
117 *
118 * Answer messages must consist of one header and one or more resource records. An additional question section or
119 * message compression (RFC 1035 4.1.4) are not supported.
120* \returns allocated answer message on success, NULL on error.
121 */
122struct osmo_mdns_msg_answer *osmo_mdns_msg_answer_decode(void *ctx, const uint8_t *data, size_t data_len)
123{
124 struct osmo_mdns_rfc_header hdr = {};
125 size_t hdr_len = sizeof(struct osmo_mdns_rfc_header);
126 struct osmo_mdns_msg_answer *ret = talloc_zero(ctx, struct osmo_mdns_msg_answer);
127
128 /* Parse header section */
129 if (data_len < hdr_len || osmo_mdns_rfc_header_decode(data, hdr_len, &hdr) != 0 || hdr.qr != 1)
130 goto error;
131 ret->id = hdr.id;
132 data_len -= hdr_len;
133 data += hdr_len;
134
135 /* Parse resource records */
136 INIT_LLIST_HEAD(&ret->records);
137 while (data_len) {
138 size_t record_len;
139 struct osmo_mdns_rfc_record *rec;
140 struct osmo_mdns_record* ret_record;
141
142 rec = osmo_mdns_rfc_record_decode(ret, data, data_len, &record_len);
143 if (!rec)
144 goto error;
145
146 /* Copy domain to ret */
147 if (ret->domain) {
148 if (strcmp(ret->domain, rec->domain) != 0) {
149 LOGP(DMSLOOKUP, LOGL_ERROR, "domain mismatch in resource records ('%s' vs '%s')\n",
150 ret->domain, rec->domain);
151 goto error;
152 }
153 }
154 else
155 ret->domain = talloc_strdup(ret, rec->domain);
156
157 /* Add simplified record to ret */
158 ret_record = talloc_zero(ret, struct osmo_mdns_record);
159 ret_record->type = rec->type;
160 ret_record->length = rec->rdlength;
161 ret_record->data = talloc_memdup(ret_record, rec->rdata, rec->rdlength);
162 llist_add_tail(&ret_record->list, &ret->records);
163
164 data += record_len;
165 data_len -= record_len;
166 talloc_free(rec);
167 }
168
169 /* Verify record count */
170 if (llist_count(&ret->records) != hdr.ancount) {
171 LOGP(DMSLOOKUP, LOGL_ERROR, "amount of parsed records (%i) doesn't match count in header (%i)\n",
172 llist_count(&ret->records), hdr.ancount);
173 goto error;
174 }
175
176 return ret;
177error:
178 talloc_free(ret);
179 return NULL;
180}
181
182/*! Get a TXT resource record, which stores a key=value string.
183 * \returns allocated resource record on success, NULL on error.
184 */
185static struct osmo_mdns_record *_osmo_mdns_record_txt_encode(void *ctx, const char *key, const char *value)
186{
187 struct osmo_mdns_record *ret = talloc_zero(ctx, struct osmo_mdns_record);
188 size_t len = strlen(key) + 1 + strlen(value);
189
190 if (len > OSMO_MDNS_RFC_MAX_CHARACTER_STRING_LEN - 1)
191 return NULL;
192
193 /* redundant len is required, see RFC 1035 3.3.14 and 3.3. */
194 ret->data = (uint8_t *)talloc_asprintf(ctx, "%c%s=%s", (char)len, key, value);
195 if (!ret->data)
196 return NULL;
197 ret->type = OSMO_MDNS_RFC_RECORD_TYPE_TXT;
198 ret->length = len + 1;
199 return ret;
200}
201
202/*! Get a TXT resource record, which stores a key=value string, but build value from a format string.
203 * \returns allocated resource record on success, NULL on error.
204 */
205struct osmo_mdns_record *osmo_mdns_record_txt_keyval_encode(void *ctx, const char *key, const char *value_fmt, ...)
206{
207 va_list ap;
208 char *value = NULL;
209 struct osmo_mdns_record *r;
210
211 if (!value_fmt)
212 return _osmo_mdns_record_txt_encode(ctx, key, "");
213
214 va_start(ap, value_fmt);
215 value = talloc_vasprintf(ctx, value_fmt, ap);
Oliver Smithb1775162020-01-13 14:57:18 +0100216 va_end(ap);
Oliver Smith3a9f2672019-11-20 10:56:35 +0100217 if (!value)
218 return NULL;
Oliver Smith3a9f2672019-11-20 10:56:35 +0100219 r = _osmo_mdns_record_txt_encode(ctx, key, value);
220 talloc_free(value);
221 return r;
222}
223
224/*! Decode a TXT resource record, which stores a key=value string.
225 * \returns 0 on success, -EINVAL on error.
226 */
227int osmo_mdns_record_txt_keyval_decode(const struct osmo_mdns_record *rec,
228 char *key_buf, size_t key_size, char *value_buf, size_t value_size)
229{
230 const char *key_value;
231 const char *key_value_end;
232 const char *sep;
233 const char *value;
234
235 if (rec->type != OSMO_MDNS_RFC_RECORD_TYPE_TXT)
236 return -EINVAL;
237
238 key_value = (const char *)rec->data;
239 key_value_end = key_value + rec->length;
240
241 /* Verify and then skip the redundant string length byte */
242 if (*key_value != rec->length - 1)
243 return -EINVAL;
244 key_value++;
245
246 if (key_value >= key_value_end)
247 return -EINVAL;
248
249 /* Find equals sign */
250 sep = osmo_strnchr(key_value, key_value_end - key_value, '=');
251 if (!sep)
252 return -EINVAL;
253
254 /* Parse key */
255 osmo_print_n(key_buf, key_size, key_value, sep - key_value);
256
257 /* Parse value */
258 value = sep + 1;
259 osmo_print_n(value_buf, value_size, value, key_value_end - value);
260 return 0;
261}