Oliver Smith | 3a9f267 | 2019-11-20 10:56:35 +0100 | [diff] [blame] | 1 | /* 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 | */ |
| 31 | int 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 | */ |
| 52 | struct 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. */ |
| 76 | void 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 | */ |
| 89 | int 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 | */ |
| 122 | struct 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; |
| 177 | error: |
| 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 | */ |
| 185 | static 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 | */ |
| 205 | struct 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); |
| 216 | if (!value) |
| 217 | return NULL; |
| 218 | va_end(ap); |
| 219 | 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 | */ |
| 227 | int 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 | } |