mirror of
https://github.com/cesanta/mongoose.git
synced 2026-10-28 12:30:58 +00:00
382 lines
9.6 KiB
C
382 lines
9.6 KiB
C
/*
|
|
* Copyright (c) 2014 Cesanta Software Limited
|
|
* All rights reserved
|
|
*/
|
|
|
|
#if MG_ENABLE_DNS
|
|
|
|
#include "mg_internal.h"
|
|
#include "mg_dns.h"
|
|
|
|
static int mg_dns_tid = 0xa0;
|
|
|
|
struct mg_dns_header {
|
|
uint16_t transaction_id;
|
|
uint16_t flags;
|
|
uint16_t num_questions;
|
|
uint16_t num_answers;
|
|
uint16_t num_authority_prs;
|
|
uint16_t num_other_prs;
|
|
};
|
|
|
|
struct mg_dns_resource_record *mg_dns_next_record(
|
|
struct mg_dns_message *msg, int query,
|
|
struct mg_dns_resource_record *prev) {
|
|
struct mg_dns_resource_record *rr;
|
|
|
|
for (rr = (prev == NULL ? msg->answers : prev + 1);
|
|
rr - msg->answers < msg->num_answers; rr++) {
|
|
if (rr->rtype == query) {
|
|
return rr;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
int mg_dns_parse_record_data(struct mg_dns_message *msg,
|
|
struct mg_dns_resource_record *rr, void *data,
|
|
size_t data_len) {
|
|
switch (rr->rtype) {
|
|
case MG_DNS_A_RECORD:
|
|
if (data_len < sizeof(struct in_addr)) {
|
|
return -1;
|
|
}
|
|
if (rr->rdata.p + data_len > msg->pkt.p + msg->pkt.len) {
|
|
return -1;
|
|
}
|
|
memcpy(data, rr->rdata.p, data_len);
|
|
return 0;
|
|
#if MG_ENABLE_IPV6
|
|
case MG_DNS_AAAA_RECORD:
|
|
if (data_len < sizeof(struct in6_addr)) {
|
|
return -1; /* LCOV_EXCL_LINE */
|
|
}
|
|
memcpy(data, rr->rdata.p, data_len);
|
|
return 0;
|
|
#endif
|
|
case MG_DNS_CNAME_RECORD:
|
|
mg_dns_uncompress_name(msg, &rr->rdata, (char *) data, data_len);
|
|
return 0;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
int mg_dns_insert_header(struct mbuf *io, size_t pos,
|
|
struct mg_dns_message *msg) {
|
|
struct mg_dns_header header;
|
|
|
|
memset(&header, 0, sizeof(header));
|
|
header.transaction_id = msg->transaction_id;
|
|
header.flags = htons(msg->flags);
|
|
header.num_questions = htons(msg->num_questions);
|
|
header.num_answers = htons(msg->num_answers);
|
|
|
|
return mbuf_insert(io, pos, &header, sizeof(header));
|
|
}
|
|
|
|
int mg_dns_copy_questions(struct mbuf *io, struct mg_dns_message *msg) {
|
|
unsigned char *begin, *end;
|
|
struct mg_dns_resource_record *last_q;
|
|
if (msg->num_questions <= 0) return 0;
|
|
begin = (unsigned char *) msg->pkt.p + sizeof(struct mg_dns_header);
|
|
last_q = &msg->questions[msg->num_questions - 1];
|
|
end = (unsigned char *) last_q->name.p + last_q->name.len + 4;
|
|
return mbuf_append(io, begin, end - begin);
|
|
}
|
|
|
|
int mg_dns_encode_name_s(struct mbuf *io, struct mg_str name) {
|
|
const char *s;
|
|
unsigned char n;
|
|
size_t pos = io->len;
|
|
|
|
do {
|
|
if ((s = mg_strchr(name, '.')) == NULL) {
|
|
s = name.p + name.len;
|
|
}
|
|
|
|
if (s - name.p > 127) {
|
|
return -1; /* TODO(mkm) cover */
|
|
}
|
|
n = s - name.p; /* chunk length */
|
|
mbuf_append(io, &n, 1); /* send length */
|
|
mbuf_append(io, name.p, n);
|
|
|
|
if (n < name.len && *s == '.') {
|
|
n++;
|
|
}
|
|
|
|
name.p += n;
|
|
name.len -= n;
|
|
} while (name.len > 0);
|
|
mbuf_append(io, "\0", 1); /* Mark end of host name */
|
|
|
|
return io->len - pos;
|
|
}
|
|
|
|
int mg_dns_encode_name(struct mbuf *io, const char *name, size_t len) {
|
|
return mg_dns_encode_name_s(io, mg_mk_str_n(name, len));
|
|
}
|
|
|
|
int mg_dns_encode_record(struct mbuf *io, struct mg_dns_resource_record *rr,
|
|
const char *name, size_t nlen, const void *rdata,
|
|
size_t rlen) {
|
|
size_t pos = io->len;
|
|
uint16_t u16;
|
|
uint32_t u32;
|
|
|
|
if (rr->kind == MG_DNS_INVALID_RECORD) {
|
|
return -1; /* LCOV_EXCL_LINE */
|
|
}
|
|
|
|
if (mg_dns_encode_name(io, name, nlen) == -1) {
|
|
return -1;
|
|
}
|
|
|
|
u16 = htons(rr->rtype);
|
|
mbuf_append(io, &u16, 2);
|
|
u16 = htons(rr->rclass);
|
|
mbuf_append(io, &u16, 2);
|
|
|
|
if (rr->kind == MG_DNS_ANSWER) {
|
|
u32 = htonl(rr->ttl);
|
|
mbuf_append(io, &u32, 4);
|
|
|
|
if (rr->rtype == MG_DNS_CNAME_RECORD) {
|
|
int clen;
|
|
/* fill size after encoding */
|
|
size_t off = io->len;
|
|
mbuf_append(io, &u16, 2);
|
|
if ((clen = mg_dns_encode_name(io, (const char *) rdata, rlen)) == -1) {
|
|
return -1;
|
|
}
|
|
u16 = clen;
|
|
io->buf[off] = u16 >> 8;
|
|
io->buf[off + 1] = u16 & 0xff;
|
|
} else {
|
|
u16 = htons((uint16_t) rlen);
|
|
mbuf_append(io, &u16, 2);
|
|
mbuf_append(io, rdata, rlen);
|
|
}
|
|
}
|
|
|
|
return io->len - pos;
|
|
}
|
|
|
|
void mg_send_dns_query(struct mg_connection *nc, const char *name,
|
|
int query_type) {
|
|
struct mg_dns_message *msg =
|
|
(struct mg_dns_message *) MG_CALLOC(1, sizeof(*msg));
|
|
struct mbuf pkt;
|
|
struct mg_dns_resource_record *rr = &msg->questions[0];
|
|
|
|
DBG(("%s %d", name, query_type));
|
|
|
|
mbuf_init(&pkt, 64 /* Start small, it'll grow as needed. */);
|
|
|
|
msg->transaction_id = ++mg_dns_tid;
|
|
msg->flags = 0x100;
|
|
msg->num_questions = 1;
|
|
|
|
mg_dns_insert_header(&pkt, 0, msg);
|
|
|
|
rr->rtype = query_type;
|
|
rr->rclass = 1; /* Class: inet */
|
|
rr->kind = MG_DNS_QUESTION;
|
|
|
|
if (mg_dns_encode_record(&pkt, rr, name, strlen(name), NULL, 0) == -1) {
|
|
/* TODO(mkm): return an error code */
|
|
goto cleanup; /* LCOV_EXCL_LINE */
|
|
}
|
|
|
|
/* TCP DNS requires messages to be prefixed with len */
|
|
if (!(nc->flags & MG_F_UDP)) {
|
|
uint16_t len = htons((uint16_t) pkt.len);
|
|
mbuf_insert(&pkt, 0, &len, 2);
|
|
}
|
|
|
|
mg_send(nc, pkt.buf, pkt.len);
|
|
mbuf_free(&pkt);
|
|
|
|
cleanup:
|
|
MG_FREE(msg);
|
|
}
|
|
|
|
static unsigned char *mg_parse_dns_resource_record(
|
|
unsigned char *data, unsigned char *end, struct mg_dns_resource_record *rr,
|
|
int reply) {
|
|
unsigned char *name = data;
|
|
int chunk_len, data_len;
|
|
|
|
while (data < end && (chunk_len = *data)) {
|
|
if (((unsigned char *) data)[0] & 0xc0) {
|
|
data += 1;
|
|
break;
|
|
}
|
|
data += chunk_len + 1;
|
|
}
|
|
|
|
if (data > end - 5) {
|
|
return NULL;
|
|
}
|
|
|
|
rr->name.p = (char *) name;
|
|
rr->name.len = data - name + 1;
|
|
data++;
|
|
|
|
rr->rtype = data[0] << 8 | data[1];
|
|
data += 2;
|
|
|
|
rr->rclass = data[0] << 8 | data[1];
|
|
data += 2;
|
|
|
|
rr->kind = reply ? MG_DNS_ANSWER : MG_DNS_QUESTION;
|
|
if (reply) {
|
|
if (data >= end - 6) {
|
|
return NULL;
|
|
}
|
|
|
|
rr->ttl = (uint32_t) data[0] << 24 | (uint32_t) data[1] << 16 |
|
|
data[2] << 8 | data[3];
|
|
data += 4;
|
|
|
|
data_len = *data << 8 | *(data + 1);
|
|
data += 2;
|
|
|
|
rr->rdata.p = (char *) data;
|
|
rr->rdata.len = data_len;
|
|
data += data_len;
|
|
}
|
|
return data;
|
|
}
|
|
|
|
int mg_parse_dns(const char *buf, int len, struct mg_dns_message *msg) {
|
|
struct mg_dns_header *header = (struct mg_dns_header *) buf;
|
|
unsigned char *data = (unsigned char *) buf + sizeof(*header);
|
|
unsigned char *end = (unsigned char *) buf + len;
|
|
int i;
|
|
|
|
memset(msg, 0, sizeof(*msg));
|
|
msg->pkt.p = buf;
|
|
msg->pkt.len = len;
|
|
|
|
if (len < (int) sizeof(*header)) return -1;
|
|
|
|
msg->transaction_id = header->transaction_id;
|
|
msg->flags = ntohs(header->flags);
|
|
msg->num_questions = ntohs(header->num_questions);
|
|
if (msg->num_questions > (int) ARRAY_SIZE(msg->questions)) {
|
|
msg->num_questions = (int) ARRAY_SIZE(msg->questions);
|
|
}
|
|
msg->num_answers = ntohs(header->num_answers);
|
|
if (msg->num_answers > (int) ARRAY_SIZE(msg->answers)) {
|
|
msg->num_answers = (int) ARRAY_SIZE(msg->answers);
|
|
}
|
|
|
|
for (i = 0; i < msg->num_questions; i++) {
|
|
data = mg_parse_dns_resource_record(data, end, &msg->questions[i], 0);
|
|
if (data == NULL) return -1;
|
|
}
|
|
|
|
for (i = 0; i < msg->num_answers; i++) {
|
|
data = mg_parse_dns_resource_record(data, end, &msg->answers[i], 1);
|
|
if (data == NULL) return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
size_t mg_dns_uncompress_name(struct mg_dns_message *msg, struct mg_str *name,
|
|
char *dst, int dst_len) {
|
|
int chunk_len, num_ptrs = 0;
|
|
char *old_dst = dst;
|
|
const unsigned char *data = (unsigned char *) name->p;
|
|
const unsigned char *end = (unsigned char *) msg->pkt.p + msg->pkt.len;
|
|
|
|
if (data >= end) {
|
|
return 0;
|
|
}
|
|
|
|
while ((chunk_len = *data++)) {
|
|
int leeway = dst_len - (dst - old_dst);
|
|
if (data >= end) {
|
|
return 0;
|
|
}
|
|
|
|
if ((chunk_len & 0xc0) == 0xc0) {
|
|
uint16_t off = (data[-1] & (~0xc0)) << 8 | data[0];
|
|
if (off >= msg->pkt.len) {
|
|
return 0;
|
|
}
|
|
/* Basic circular loop avoidance: allow up to 16 pointer hops. */
|
|
if (++num_ptrs > 15) {
|
|
return 0;
|
|
}
|
|
data = (unsigned char *) msg->pkt.p + off;
|
|
continue;
|
|
}
|
|
if (chunk_len > 63) {
|
|
return 0;
|
|
}
|
|
if (chunk_len > leeway) {
|
|
chunk_len = leeway;
|
|
}
|
|
|
|
if (data + chunk_len >= end) {
|
|
return 0;
|
|
}
|
|
|
|
memcpy(dst, data, chunk_len);
|
|
data += chunk_len;
|
|
dst += chunk_len;
|
|
leeway -= chunk_len;
|
|
if (leeway == 0) {
|
|
return dst - old_dst;
|
|
}
|
|
*dst++ = '.';
|
|
}
|
|
|
|
if (dst != old_dst) {
|
|
*--dst = 0;
|
|
}
|
|
return dst - old_dst;
|
|
}
|
|
|
|
static void dns_handler(struct mg_connection *nc, int ev,
|
|
void *ev_data MG_UD_ARG(void *user_data)) {
|
|
struct mbuf *io = &nc->recv_mbuf;
|
|
struct mg_dns_message msg;
|
|
|
|
/* Pass low-level events to the user handler */
|
|
nc->handler(nc, ev, ev_data MG_UD_ARG(user_data));
|
|
|
|
switch (ev) {
|
|
case MG_EV_RECV:
|
|
if (!(nc->flags & MG_F_UDP)) {
|
|
mbuf_remove(&nc->recv_mbuf, 2);
|
|
}
|
|
if (mg_parse_dns(nc->recv_mbuf.buf, nc->recv_mbuf.len, &msg) == -1) {
|
|
/* reply + recursion allowed + format error */
|
|
memset(&msg, 0, sizeof(msg));
|
|
msg.flags = 0x8081;
|
|
mg_dns_insert_header(io, 0, &msg);
|
|
if (!(nc->flags & MG_F_UDP)) {
|
|
uint16_t len = htons((uint16_t) io->len);
|
|
mbuf_insert(io, 0, &len, 2);
|
|
}
|
|
mg_send(nc, io->buf, io->len);
|
|
} else {
|
|
/* Call user handler with parsed message */
|
|
nc->handler(nc, MG_DNS_MESSAGE, &msg MG_UD_ARG(user_data));
|
|
}
|
|
mbuf_remove(io, io->len);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void mg_set_protocol_dns(struct mg_connection *nc) {
|
|
nc->proto_handler = dns_handler;
|
|
}
|
|
|
|
#endif /* MG_ENABLE_DNS */
|