943 lines
24 KiB
C
943 lines
24 KiB
C
/*
|
|
Copyright (c) 2016-2020 Chung, Hyung-Hwan. All rights reserved.
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions
|
|
are met:
|
|
1. Redistributions of source code must retain the above copyright
|
|
notice, this list of conditions and the following disclaimer.
|
|
2. Redistributions in binary form must reproduce the above copyright
|
|
notice, this list of conditions and the following disclaimer in the
|
|
documentation and/or other materials provided with the distribution.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
|
|
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <hio-dns.h>
|
|
#include "hio-prv.h"
|
|
|
|
/* ----------------------------------------------------------------------- */
|
|
|
|
#define DN_AT_END(ptr) (ptr[0] == '\0' || (ptr[0] == '.' && ptr[1] == '\0'))
|
|
|
|
static hio_oow_t to_dn (const hio_bch_t* str, hio_uint8_t* buf)
|
|
{
|
|
hio_uint8_t* bp = buf;
|
|
/*HIO_ASSERT (HIO_SIZEOF(hio_uint8_t) == HIO_SIZEOF(hio_bch_t));*/
|
|
|
|
if (str && !DN_AT_END(str))
|
|
{
|
|
hio_uint8_t* lp;
|
|
hio_oow_t len;
|
|
const hio_bch_t* seg;
|
|
const hio_bch_t* cur = str - 1;
|
|
|
|
do
|
|
{
|
|
lp = bp++;
|
|
|
|
seg = ++cur;
|
|
while (*cur != '\0' && *cur != '.')
|
|
{
|
|
*bp++ = *cur;
|
|
cur++;
|
|
}
|
|
len = cur - seg;
|
|
if (len <= 0 || len > 63) return 0;
|
|
|
|
*lp = (hio_uint8_t)len;
|
|
}
|
|
while (!DN_AT_END(cur));
|
|
}
|
|
|
|
*bp++ = 0;
|
|
|
|
/* the length includes the terminating zero. */
|
|
return bp - buf;
|
|
}
|
|
|
|
static hio_oow_t to_dn_capa (const hio_bch_t* str)
|
|
{
|
|
hio_oow_t capa = 0;
|
|
|
|
/*HIO_ASSERT (HIO_SIZEOF(hio_uint8_t) == HIO_SIZEOF(hio_bch_t));*/
|
|
|
|
if (str && !DN_AT_END(str))
|
|
{
|
|
hio_oow_t len;
|
|
const hio_bch_t* seg;
|
|
const hio_bch_t* cur = str - 1;
|
|
|
|
do
|
|
{
|
|
capa++;
|
|
|
|
seg = ++cur;
|
|
while (*cur != '\0' && *cur != '.')
|
|
{
|
|
capa++;
|
|
cur++;
|
|
}
|
|
len = cur - seg;
|
|
if (len <= 0 || len > 63) return 0;
|
|
}
|
|
while (!DN_AT_END(cur));
|
|
}
|
|
|
|
capa++;
|
|
|
|
/* the length includes the terminating zero. */
|
|
return capa;
|
|
}
|
|
|
|
static hio_oow_t dn_length (hio_uint8_t* ptr, hio_oow_t len)
|
|
{
|
|
hio_uint8_t* curptr;
|
|
hio_oow_t curlen, seglen;
|
|
|
|
curptr = ptr;
|
|
curlen = len;
|
|
|
|
do
|
|
{
|
|
if (curlen <= 0) return 0;
|
|
|
|
seglen = *curptr++;
|
|
curlen = curlen - 1;
|
|
if (seglen == 0) break;
|
|
else if (seglen > curlen || seglen > 63) return 0;
|
|
|
|
curptr += seglen;
|
|
curlen -= seglen;
|
|
}
|
|
while (1);
|
|
|
|
return curptr - ptr;
|
|
}
|
|
|
|
/* ----------------------------------------------------------------------- */
|
|
|
|
static int parse_domain_name (hio_t* hio, hio_dns_pkt_info_t* pi)
|
|
{
|
|
hio_oow_t seglen;
|
|
hio_uint8_t* xptr;
|
|
|
|
if (HIO_UNLIKELY(pi->_ptr >= pi->_end)) goto oops;
|
|
xptr = HIO_NULL;
|
|
|
|
if ((seglen = *pi->_ptr++) == 0)
|
|
{
|
|
if (pi->_rrdptr) pi->_rrdptr[0] = '\0';
|
|
pi->_rrdlen++; /* for a terminating null */
|
|
return 0;
|
|
}
|
|
|
|
do
|
|
{
|
|
if (HIO_LIKELY(seglen < 64))
|
|
{
|
|
/* normal. 00XXXXXXXX */
|
|
normal:
|
|
if (pi->_rrdptr)
|
|
{
|
|
HIO_MEMCPY (pi->_rrdptr, pi->_ptr, seglen);
|
|
pi->_rrdptr += seglen + 1; /* +1 for '.' */
|
|
pi->_rrdptr[-1] = '.';
|
|
}
|
|
|
|
pi->_rrdlen += seglen + 1; /* +1 for '.' */
|
|
pi->_ptr += seglen;
|
|
if (HIO_UNLIKELY(pi->_ptr >= pi->_end)) goto oops;
|
|
}
|
|
else if (seglen >= 192)
|
|
{
|
|
/* compressed. 11XXXXXXXX XXXXXXXX */
|
|
hio_oow_t offset;
|
|
|
|
if (HIO_UNLIKELY(pi->_ptr >= pi->_end)) goto oops;
|
|
offset = ((seglen & 0x3F) << 8) | *pi->_ptr++;
|
|
|
|
/*if (HIO_UNLIKELY(pi->_ptr >= pi->_end)) goto oops; <- this condition can be true if the function is called for the domain name at the back of the last RR */
|
|
|
|
seglen = pi->_start[offset];
|
|
if (seglen >= 64) goto oops; /* the pointed position must not contain another pointer */
|
|
|
|
if (!xptr) xptr = pi->_ptr; /* some later parts can also be a pointer again. so xptr, once set, must not be set again */
|
|
pi->_ptr = &pi->_start[offset + 1];
|
|
if (HIO_UNLIKELY(pi->_ptr >= pi->_end)) goto oops;
|
|
|
|
goto normal;
|
|
}
|
|
else if (seglen >= 128)
|
|
{
|
|
/* 128 to 191. 10XXXXXXXX */
|
|
goto oops;
|
|
}
|
|
else
|
|
{
|
|
/* 64 to 127. 01XXXXXXXX */
|
|
goto oops;
|
|
}
|
|
}
|
|
while ((seglen = *pi->_ptr++) > 0);
|
|
|
|
if (pi->_rrdptr) pi->_rrdptr[-1] = '\0';
|
|
|
|
if (xptr) pi->_ptr = xptr;
|
|
return 0;
|
|
|
|
oops:
|
|
hio_seterrnum (hio, HIO_EINVAL);
|
|
return -1;
|
|
}
|
|
|
|
static int parse_question_rr (hio_t* hio, hio_oow_t pos, hio_dns_pkt_info_t* pi)
|
|
{
|
|
hio_dns_qrtr_t* qrtr;
|
|
hio_uint8_t* xrrdptr;
|
|
|
|
xrrdptr = pi->_rrdptr;
|
|
if (parse_domain_name(hio, pi) <= -1) return -1;
|
|
|
|
qrtr = (hio_dns_qrtr_t*)pi->_ptr;
|
|
if (HIO_UNLIKELY(pi->_ptr > pi->_end || pi->_end - pi->_ptr < HIO_SIZEOF(*qrtr))) goto oops;
|
|
|
|
pi->_ptr += HIO_SIZEOF(*qrtr);
|
|
/*pi->_rrdlen += HIO_SIZEOF(*qrtr);*/
|
|
|
|
if (pi->_rrdptr)
|
|
{
|
|
hio_dns_bqr_t* bqr;
|
|
bqr = pi->rr.qd;
|
|
bqr[pos].qname = (hio_bch_t*)xrrdptr;
|
|
bqr[pos].qtype = hio_ntoh16(qrtr->qtype);
|
|
bqr[pos].qclass = hio_ntoh16(qrtr->qclass);
|
|
}
|
|
|
|
return 0;
|
|
|
|
oops:
|
|
hio_seterrnum (hio, HIO_EINVAL);
|
|
return -1;
|
|
}
|
|
|
|
static int parse_answer_rr (hio_t* hio, hio_dns_rr_part_t rr_part, hio_oow_t pos, hio_dns_pkt_info_t* pi)
|
|
{
|
|
hio_dns_rrtr_t* rrtr;
|
|
hio_uint16_t qtype, dlen;
|
|
hio_uint8_t* xrrdptr, *xrrdptr2;
|
|
|
|
xrrdptr = pi->_rrdptr;
|
|
if (parse_domain_name(hio, pi) <= -1) return -1;
|
|
|
|
rrtr = (hio_dns_rrtr_t*)pi->_ptr;
|
|
if (HIO_UNLIKELY(pi->_ptr > pi->_end || pi->_end - pi->_ptr < HIO_SIZEOF(*rrtr))) goto oops;
|
|
|
|
pi->_ptr += HIO_SIZEOF(*rrtr);
|
|
dlen = hio_ntoh16(rrtr->dlen);
|
|
if (HIO_UNLIKELY(pi->_ptr > pi->_end || pi->_end - pi->_ptr < dlen)) goto oops;
|
|
|
|
qtype = hio_ntoh16(rrtr->rrtype);
|
|
|
|
xrrdptr2 = pi->_rrdptr;
|
|
|
|
switch (qtype)
|
|
{
|
|
case HIO_DNS_RRT_OPT:
|
|
{
|
|
hio_uint16_t eopt_tot_len, eopt_len;
|
|
hio_dns_eopt_t* eopt;
|
|
|
|
/* RFC 6891
|
|
The extended RCODE and flags, which OPT stores in the RR Time to Live
|
|
(TTL) field, are structured as follows:
|
|
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
0: | EXTENDED-RCODE | VERSION |
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
2: | DO| Z |
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
EXTENDED-RCODE
|
|
Forms the upper 8 bits of extended 12-bit RCODE (together with the
|
|
4 bits defined in [RFC1035]. Note that EXTENDED-RCODE value 0
|
|
indicates that an unextended RCODE is in use (values 0 through
|
|
15).
|
|
*/
|
|
|
|
/* TODO: do i need to check if rrname is <ROOT>? */
|
|
/* TODO: do i need to check if rr_part is HIO_DNS_RR_PART_ADDITIONAL? the OPT pseudo-RR may exist in the ADDITIONAL section only */
|
|
/* TODO: do i need to check if there is more than 1 OPT RRs */
|
|
pi->edns.exist++; /* you may treat this as the number of OPT RRs */
|
|
pi->edns.uplen = hio_ntoh16(rrtr->rrclass);
|
|
pi->hdr.rcode |= (rrtr->ttl >> 24);
|
|
pi->edns.version = (rrtr->ttl >> 16) & 0xFF;
|
|
pi->edns.dnssecok = ((rrtr->ttl & 0x8000) >> 15);
|
|
/*if ((rrtr->ttl & 0x7FFF) != 0) goto oops;*/ /* Z not 0 - ignore this for now */
|
|
|
|
eopt = (hio_dns_eopt_t*)(rrtr + 1);
|
|
eopt_tot_len = dlen;
|
|
while (eopt_tot_len > 0)
|
|
{
|
|
if (eopt_tot_len < HIO_SIZEOF(hio_dns_eopt_t)) goto oops;
|
|
|
|
eopt_len = hio_ntoh16(eopt->dlen);
|
|
if (eopt_tot_len - HIO_SIZEOF(hio_dns_eopt_t) < eopt_len) goto oops; /* wrong eopt length */
|
|
|
|
if (eopt->code == HIO_CONST_HTON16(HIO_DNS_EOPT_COOKIE))
|
|
{
|
|
if (eopt_len == HIO_DNS_COOKIE_CLIENT_LEN)
|
|
{
|
|
/* client cookie only */
|
|
HIO_MEMCPY (pi->edns.cookie.data.client, eopt + 1, eopt_len);
|
|
pi->edns.cookie.client_len = eopt_len;
|
|
pi->edns.cookie.server_len = 0;
|
|
}
|
|
else if (eopt_len >= (HIO_DNS_COOKIE_CLIENT_LEN + HIO_DNS_COOKIE_SERVER_MIN_LEN) &&
|
|
eopt_len <= (HIO_DNS_COOKIE_CLIENT_LEN + HIO_DNS_COOKIE_SERVER_MAX_LEN))
|
|
{
|
|
/* both client and server cookie */
|
|
HIO_MEMCPY (&pi->edns.cookie.data, eopt + 1, eopt_len);
|
|
pi->edns.cookie.client_len = HIO_DNS_COOKIE_CLIENT_LEN;
|
|
pi->edns.cookie.server_len = eopt_len - HIO_DNS_COOKIE_CLIENT_LEN;
|
|
}
|
|
else
|
|
{
|
|
/* wrong cookie length */
|
|
goto oops;
|
|
}
|
|
}
|
|
|
|
eopt_tot_len -= HIO_SIZEOF(hio_dns_eopt_t) + eopt_len;
|
|
eopt = (hio_dns_eopt_t*)((hio_uint8_t*)eopt + HIO_SIZEOF(hio_dns_eopt_t) + eopt_len);
|
|
}
|
|
|
|
goto verbatim; /* keep the entire option data including cookies */
|
|
}
|
|
|
|
case HIO_DNS_RRT_A:
|
|
if (HIO_UNLIKELY(dlen != HIO_SIZEOF(hio_ip4ad_t))) goto oops;
|
|
goto verbatim;
|
|
|
|
case HIO_DNS_RRT_AAAA:
|
|
if (HIO_UNLIKELY(dlen != HIO_SIZEOF(hio_ip6ad_t))) goto oops;
|
|
goto verbatim;
|
|
|
|
/*case HIO_DNS_RRT_MB:
|
|
case HIO_DNS_RRT_MD:
|
|
case HIO_DNS_RRT_MF:
|
|
case HIO_DNS_RRT_MG:
|
|
case HIO_DNS_RRT_MR:*/
|
|
case HIO_DNS_RRT_CNAME:
|
|
case HIO_DNS_RRT_NS:
|
|
case HIO_DNS_RRT_PTR:
|
|
{
|
|
#if !defined(HIO_BUILD_RELEASE)
|
|
hio_uint8_t* xptr = pi->_ptr;
|
|
#endif
|
|
if (parse_domain_name(hio, pi) <= -1) goto oops;
|
|
HIO_ASSERT (hio, pi->_ptr == xptr + dlen);
|
|
break;
|
|
}
|
|
|
|
case HIO_DNS_RRT_MX:
|
|
{
|
|
#if !defined(HIO_BUILD_RELEASE)
|
|
hio_uint8_t* xptr = pi->_ptr;
|
|
#endif
|
|
hio_dns_brrd_mx_t* mx;
|
|
|
|
pi->_rrdlen += HIO_SIZEOF(*mx);
|
|
if (HIO_UNLIKELY(pi->_end - pi->_ptr < 2)) goto oops;
|
|
|
|
if (pi->_rrdptr)
|
|
{
|
|
mx = (hio_dns_brrd_mx_t*)pi->_rrdptr;
|
|
pi->_rrdptr += HIO_SIZEOF(*mx);
|
|
|
|
HIO_MEMCPY (&mx->preference, pi->_ptr, 2); pi->_ptr += 2;
|
|
|
|
mx->preference = hio_ntoh16(mx->preference);
|
|
mx->exchange = (hio_bch_t*)pi->_rrdptr;
|
|
if (parse_domain_name(hio, pi) <= -1) goto oops;
|
|
}
|
|
else
|
|
{
|
|
pi->_ptr += 2;
|
|
if (parse_domain_name(hio, pi) <= -1) goto oops;
|
|
}
|
|
|
|
HIO_ASSERT (hio, pi->_ptr == xptr + dlen);
|
|
break;
|
|
}
|
|
|
|
case HIO_DNS_RRT_SOA:
|
|
{
|
|
#if !defined(HIO_BUILD_RELEASE)
|
|
hio_uint8_t* xptr = pi->_ptr;
|
|
#endif
|
|
hio_dns_brrd_soa_t* soa;
|
|
|
|
pi->_rrdlen += HIO_SIZEOF(*soa);
|
|
if (pi->_rrdptr)
|
|
{
|
|
soa = (hio_dns_brrd_soa_t*)pi->_rrdptr;
|
|
pi->_rrdptr += HIO_SIZEOF(*soa);
|
|
|
|
soa->mname = (hio_bch_t*)pi->_rrdptr;
|
|
if (parse_domain_name(hio, pi) <= -1) goto oops;
|
|
|
|
soa->rname = (hio_bch_t*)pi->_rrdptr;
|
|
if (parse_domain_name(hio, pi) <= -1) goto oops;
|
|
|
|
if (HIO_UNLIKELY(pi->_end - pi->_ptr < 20)) goto oops;
|
|
HIO_MEMCPY (&soa->serial, pi->_ptr, 20);
|
|
soa->serial = hio_ntoh32(soa->serial);
|
|
soa->refresh = hio_ntoh32(soa->refresh);
|
|
soa->retry = hio_ntoh32(soa->retry);
|
|
soa->expire = hio_ntoh32(soa->expire);
|
|
soa->minimum = hio_ntoh32(soa->minimum);
|
|
}
|
|
else
|
|
{
|
|
if (parse_domain_name(hio, pi) <= -1) goto oops;
|
|
if (parse_domain_name(hio, pi) <= -1) goto oops;
|
|
if (HIO_UNLIKELY(pi->_end - pi->_ptr < 20)) goto oops;
|
|
}
|
|
pi->_ptr += 20;
|
|
|
|
HIO_ASSERT (hio, pi->_ptr == xptr + dlen);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
verbatim:
|
|
pi->_ptr += dlen;
|
|
pi->_rrdlen += dlen;
|
|
if (pi->_rrdptr)
|
|
{
|
|
HIO_MEMCPY (pi->_rrdptr, rrtr + 1, dlen); /* copy actual data */
|
|
pi->_rrdptr += dlen;
|
|
}
|
|
}
|
|
|
|
if (pi->_rrdptr)
|
|
{
|
|
/* store information about the actual record */
|
|
hio_dns_brr_t* brr;
|
|
|
|
switch (rr_part)
|
|
{
|
|
case HIO_DNS_RR_PART_ANSWER: brr = pi->rr.an; break;
|
|
case HIO_DNS_RR_PART_AUTHORITY: brr = pi->rr.ns; break;
|
|
case HIO_DNS_RR_PART_ADDITIONAL: brr = pi->rr.ar; break;
|
|
default: goto oops;
|
|
}
|
|
|
|
brr[pos].part = rr_part;
|
|
brr[pos].rrname = (hio_bch_t*)xrrdptr;
|
|
brr[pos].rrtype = hio_ntoh16(rrtr->rrtype);
|
|
brr[pos].rrclass = hio_ntoh16(rrtr->rrclass);
|
|
brr[pos].ttl = hio_ntoh32(rrtr->ttl);
|
|
brr[pos].dptr = xrrdptr2;
|
|
/* this length may be different from the length in the header as transformation is performed on some RR data.
|
|
* for a domain name, it's inclusive of the termining null. */
|
|
brr[pos].dlen = pi->_rrdptr - xrrdptr2;
|
|
}
|
|
|
|
return 0;
|
|
|
|
oops:
|
|
hio_seterrnum (hio, HIO_EINVAL);
|
|
return -1;
|
|
}
|
|
|
|
hio_dns_pkt_info_t* hio_dns_make_pkt_info (hio_t* hio, const hio_dns_pkt_t* pkt, hio_oow_t len)
|
|
{
|
|
hio_uint16_t i;
|
|
hio_dns_pkt_info_t pib, * pii;
|
|
|
|
HIO_ASSERT (hio, len >= HIO_SIZEOF(*pkt));
|
|
|
|
HIO_MEMSET (&pib, 0, HIO_SIZEOF(pib));
|
|
|
|
/* pib is used as the initial workspace and also indicates that it's the first run.
|
|
* at the second run, pii is set to a dynamically allocated memory block large enough
|
|
* to hold actual data. */
|
|
pii = &pib;
|
|
|
|
redo:
|
|
pii->_start = (hio_uint8_t*)pkt;
|
|
pii->_end = (hio_uint8_t*)pkt + len;
|
|
pii->_ptr = (hio_uint8_t*)(pkt + 1);
|
|
|
|
pii->hdr.id = hio_ntoh16(pkt->id);
|
|
pii->hdr.qr = pkt->qr & 0x01;
|
|
pii->hdr.opcode = pkt->opcode & 0x0F;
|
|
pii->hdr.aa = pkt->aa & 0x01;
|
|
pii->hdr.tc = pkt->tc & 0x01;
|
|
pii->hdr.rd = pkt->rd & 0x01;
|
|
pii->hdr.ra = pkt->ra & 0x01;
|
|
pii->hdr.ad = pkt->ad & 0x01;
|
|
pii->hdr.cd = pkt->cd & 0x01;
|
|
pii->hdr.rcode = pkt->rcode & 0x0F;
|
|
pii->qdcount = hio_ntoh16(pkt->qdcount);
|
|
pii->ancount = hio_ntoh16(pkt->ancount);
|
|
pii->nscount = hio_ntoh16(pkt->nscount);
|
|
pii->arcount = hio_ntoh16(pkt->arcount);
|
|
|
|
for (i = 0; i < pii->qdcount; i++)
|
|
{
|
|
if (parse_question_rr(hio, i, pii) <= -1) goto oops;
|
|
}
|
|
|
|
for (i = 0; i < pii->ancount; i++)
|
|
{
|
|
if (parse_answer_rr(hio, HIO_DNS_RR_PART_ANSWER, i, pii) <= -1) goto oops;
|
|
}
|
|
|
|
for (i = 0; i < pii->nscount; i++)
|
|
{
|
|
if (parse_answer_rr(hio, HIO_DNS_RR_PART_AUTHORITY, i, pii) <= -1) goto oops;
|
|
}
|
|
|
|
for (i = 0; i < pii->arcount; i++)
|
|
{
|
|
if (parse_answer_rr(hio, HIO_DNS_RR_PART_ADDITIONAL, i, pii) <= -1) goto oops;
|
|
}
|
|
|
|
if (pii == &pib)
|
|
{
|
|
/* TODO: better buffer management... */
|
|
pii = (hio_dns_pkt_info_t*)hio_callocmem(hio, HIO_SIZEOF(*pii) + (HIO_SIZEOF(hio_dns_bqr_t) * pib.qdcount) + (HIO_SIZEOF(hio_dns_brr_t) * (pib.ancount + pib.nscount + pib.arcount)) + pib._rrdlen);
|
|
if (!pii) goto oops;
|
|
|
|
pii->rr.qd = (hio_dns_bqr_t*)(&pii[1]);
|
|
pii->rr.an = (hio_dns_brr_t*)&pii->rr.qd[pib.qdcount];
|
|
pii->rr.ns = (hio_dns_brr_t*)&pii->rr.an[pib.ancount];
|
|
pii->rr.ar = (hio_dns_brr_t*)&pii->rr.ns[pib.nscount];
|
|
pii->_rrdptr = (hio_uint8_t*)&pii->rr.ar[pib.arcount];
|
|
|
|
/* _rrdptr points to the beginning of memory where additional data will
|
|
* be held for some RRs. _rrdlen is the length of total additional data.
|
|
* the additional data refers to the data that is pointed to by the
|
|
* breakdown RRs(hio_dns_bqr_t/hio_dns_brr_t) but is not stored in them. */
|
|
|
|
goto redo;
|
|
}
|
|
|
|
return pii;
|
|
|
|
oops:
|
|
if (pii && pii != &pib) hio_freemem (hio, pii);
|
|
return HIO_NULL;
|
|
}
|
|
|
|
void hio_dns_free_pkt_info (hio_t* hio, hio_dns_pkt_info_t* pi)
|
|
{
|
|
/* TODO: better management */
|
|
hio_freemem (hio, pi);
|
|
}
|
|
|
|
|
|
/* ----------------------------------------------------------------------- */
|
|
|
|
static int encode_rrdata_in_dns_msg (hio_t* hio, const hio_dns_brr_t* rr, hio_uint16_t* dxlen, void* dptr)
|
|
{
|
|
hio_oow_t xlen; /* actual data length after encoding */
|
|
|
|
switch (rr->rrtype)
|
|
{
|
|
case HIO_DNS_RRT_A:
|
|
if (HIO_UNLIKELY(rr->dlen != HIO_SIZEOF(hio_ip4ad_t))) goto inval;
|
|
goto verbatim;
|
|
|
|
case HIO_DNS_RRT_AAAA:
|
|
if (HIO_UNLIKELY(rr->dlen != HIO_SIZEOF(hio_ip6ad_t))) goto inval;
|
|
goto verbatim;
|
|
|
|
/*case HIO_DNS_RRT_MB:
|
|
case HIO_DNS_RRT_MD:
|
|
case HIO_DNS_RRT_MF:
|
|
case HIO_DNS_RRT_MG:
|
|
case HIO_DNS_RRT_MR:*/
|
|
case HIO_DNS_RRT_CNAME:
|
|
case HIO_DNS_RRT_NS:
|
|
case HIO_DNS_RRT_PTR:
|
|
/* just a normal domain name */
|
|
if (dptr)
|
|
xlen = to_dn(rr->dptr, dptr);
|
|
else
|
|
xlen = to_dn_capa(rr->dptr);
|
|
if (HIO_UNLIKELY(xlen <= 0)) goto inval;
|
|
break;
|
|
|
|
#if 0
|
|
case HIO_DNS_RRT_HINFO:
|
|
/* cpu, os */
|
|
break;
|
|
#endif
|
|
|
|
#if 0
|
|
case HIO_DNS_RRT_MINFO:
|
|
/* rmailbx, emailbx */
|
|
#endif
|
|
xlen = rr->dlen;
|
|
break;
|
|
|
|
case HIO_DNS_RRT_MX:
|
|
{
|
|
hio_dns_brrd_mx_t* mx;
|
|
hio_oow_t tmp;
|
|
|
|
if (HIO_UNLIKELY(rr->dlen != HIO_SIZEOF(hio_dns_brrd_mx_t))) goto inval;
|
|
mx = (hio_dns_brrd_mx_t*)rr->dptr;
|
|
xlen = 0;
|
|
if (dptr)
|
|
{
|
|
hio_uint16_t ti;
|
|
|
|
ti = hio_hton16(mx->preference);
|
|
HIO_MEMCPY((hio_uint8_t*)dptr + xlen, &ti, HIO_SIZEOF(ti)); xlen += HIO_SIZEOF(ti);
|
|
|
|
tmp = to_dn(mx->exchange, (hio_uint8_t*)dptr + xlen);
|
|
if (HIO_UNLIKELY(tmp <= 0)) goto inval;
|
|
xlen += tmp;
|
|
}
|
|
else
|
|
{
|
|
xlen += 2;
|
|
|
|
tmp = to_dn_capa(mx->exchange);
|
|
if (HIO_UNLIKELY(tmp <= 0)) goto inval;
|
|
xlen += tmp;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case HIO_DNS_RRT_SOA:
|
|
{
|
|
/* soa */
|
|
hio_dns_brrd_soa_t* soa;
|
|
hio_oow_t tmp;
|
|
|
|
if (HIO_UNLIKELY(rr->dlen != HIO_SIZEOF(hio_dns_brrd_soa_t))) goto inval;
|
|
|
|
soa = (hio_dns_brrd_soa_t*)rr->dptr;
|
|
xlen = 0;
|
|
if (dptr)
|
|
{
|
|
hio_uint32_t ti;
|
|
|
|
tmp = to_dn(soa->mname, (hio_uint8_t*)dptr + xlen);
|
|
if (HIO_UNLIKELY(tmp <= 0)) goto inval;
|
|
xlen += tmp;
|
|
|
|
tmp = to_dn(soa->rname, (hio_uint8_t*)dptr + xlen);
|
|
if (HIO_UNLIKELY(tmp <= 0)) goto inval;
|
|
xlen += tmp;
|
|
|
|
ti = hio_hton32(soa->serial);
|
|
HIO_MEMCPY((hio_uint8_t*)dptr + xlen, &ti, HIO_SIZEOF(ti)); xlen += HIO_SIZEOF(ti);
|
|
ti = hio_hton32(soa->refresh);
|
|
HIO_MEMCPY((hio_uint8_t*)dptr + xlen, &ti, HIO_SIZEOF(ti)); xlen += HIO_SIZEOF(ti);
|
|
ti = hio_hton32(soa->retry);
|
|
HIO_MEMCPY((hio_uint8_t*)dptr + xlen, &ti, HIO_SIZEOF(ti)); xlen += HIO_SIZEOF(ti);
|
|
ti = hio_hton32(soa->expire);
|
|
HIO_MEMCPY((hio_uint8_t*)dptr + xlen, &ti, HIO_SIZEOF(ti)); xlen += HIO_SIZEOF(ti);
|
|
ti = hio_hton32(soa->minimum);
|
|
HIO_MEMCPY((hio_uint8_t*)dptr + xlen, &ti, HIO_SIZEOF(ti)); xlen += HIO_SIZEOF(ti);
|
|
}
|
|
else
|
|
{
|
|
tmp = to_dn_capa(soa->mname);
|
|
if (HIO_UNLIKELY(tmp <= 0)) goto inval;
|
|
xlen += tmp;
|
|
|
|
tmp = to_dn_capa(soa->rname);
|
|
if (HIO_UNLIKELY(tmp <= 0)) goto inval;
|
|
xlen += tmp;
|
|
|
|
xlen += 20;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case HIO_DNS_RRT_TXT:
|
|
case HIO_DNS_RRT_NULL:
|
|
default:
|
|
verbatim:
|
|
/* TODO: custom transformator? */
|
|
if (dptr) HIO_MEMCPY (dptr, rr->dptr, rr->dlen);
|
|
xlen = rr->dlen;
|
|
break;
|
|
}
|
|
|
|
if (HIO_UNLIKELY(xlen > HIO_TYPE_MAX(hio_uint16_t))) goto inval;
|
|
*dxlen = (hio_uint16_t)xlen;
|
|
return 0;
|
|
|
|
|
|
inval:
|
|
hio_seterrnum (hio, HIO_EINVAL);
|
|
return -1;
|
|
}
|
|
|
|
hio_dns_msg_t* hio_dns_make_msg (hio_t* hio, hio_dns_bhdr_t* bhdr, hio_dns_bqr_t* qr, hio_oow_t qr_count, hio_dns_brr_t* rr, hio_oow_t rr_count, hio_dns_bedns_t* edns, hio_oow_t xtnsize)
|
|
{
|
|
hio_oow_t dnlen, msgbufsz, pktlen, i;
|
|
hio_dns_msg_t* msg;
|
|
hio_dns_pkt_t* pkt;
|
|
hio_uint8_t* dn;
|
|
hio_dns_qrtr_t* qrtr;
|
|
hio_dns_rrtr_t* rrtr;
|
|
int rr_sect;
|
|
hio_uint32_t edns_dlen;
|
|
|
|
pktlen = HIO_SIZEOF(*pkt);
|
|
|
|
for (i = 0; i < qr_count; i++)
|
|
{
|
|
dnlen = to_dn_capa(qr[i].qname);
|
|
if (HIO_UNLIKELY(dnlen <= 0))
|
|
{
|
|
hio_seterrnum (hio, HIO_EINVAL);
|
|
return HIO_NULL;
|
|
}
|
|
pktlen += dnlen + HIO_SIZEOF(*qrtr);
|
|
}
|
|
|
|
for (i = 0; i < rr_count; i++)
|
|
{
|
|
hio_uint16_t rrdata_len;
|
|
dnlen = to_dn_capa(rr[i].rrname);
|
|
if (HIO_UNLIKELY(dnlen <= 0))
|
|
{
|
|
hio_seterrnum (hio, HIO_EINVAL);
|
|
return HIO_NULL;
|
|
}
|
|
if (HIO_UNLIKELY(encode_rrdata_in_dns_msg(hio, &rr[i], &rrdata_len, HIO_NULL) <= -1)) return HIO_NULL;
|
|
pktlen += dnlen + HIO_SIZEOF(*rrtr) + rrdata_len;
|
|
}
|
|
|
|
edns_dlen = 0;
|
|
if (edns)
|
|
{
|
|
hio_dns_beopt_t* beopt;
|
|
|
|
pktlen += 1 + HIO_SIZEOF(*rrtr); /* edns0 OPT RR - 1 for the root name */
|
|
|
|
beopt = edns->beoptr;
|
|
for (i = 0; i < edns->beonum; i++)
|
|
{
|
|
edns_dlen += HIO_SIZEOF(hio_dns_eopt_t) + beopt->dlen;
|
|
if (HIO_UNLIKELY(edns_dlen > HIO_TYPE_MAX(hio_uint16_t)))
|
|
{
|
|
hio_seterrbfmt (hio, HIO_EINVAL, "edns options too large");
|
|
return HIO_NULL;
|
|
}
|
|
beopt++;
|
|
}
|
|
|
|
pktlen += edns_dlen;
|
|
}
|
|
else
|
|
{
|
|
if (HIO_UNLIKELY(bhdr->rcode > 0x0F))
|
|
{
|
|
/* rcode is larger than 4 bits. but edns info is not provided */
|
|
hio_seterrbfmt (hio, HIO_EINVAL, "rcode too large without edns - %d", bhdr->rcode);
|
|
return HIO_NULL;
|
|
}
|
|
}
|
|
|
|
msgbufsz = HIO_SIZEOF(*msg) + HIO_ALIGN_POW2(pktlen, HIO_SIZEOF_VOID_P) + xtnsize;
|
|
|
|
/* TODO: msg buffer reuse */
|
|
msg = (hio_dns_msg_t*)hio_callocmem(hio, msgbufsz);
|
|
if (HIO_UNLIKELY(!msg)) return HIO_NULL;
|
|
|
|
msg->msglen = msgbufsz; /* record the instance size */
|
|
msg->pktalilen = HIO_ALIGN_POW2(pktlen, HIO_SIZEOF_VOID_P);
|
|
|
|
pkt = hio_dns_msg_to_pkt(msg); /* actual packet data begins after the message structure */
|
|
|
|
dn = (hio_uint8_t*)(pkt + 1); /* skip the dns packet header */
|
|
for (i = 0; i < qr_count; i++)
|
|
{
|
|
/* dnlen includes the ending <zero> */
|
|
dnlen = to_dn(qr[i].qname, dn);
|
|
HIO_ASSERT (hio, dnlen > 0);
|
|
|
|
qrtr = (hio_dns_qrtr_t*)(dn + dnlen);
|
|
qrtr->qtype = hio_hton16(qr[i].qtype);
|
|
qrtr->qclass = hio_hton16(qr[i].qclass);
|
|
|
|
dn = (hio_uint8_t*)(qrtr + 1);
|
|
}
|
|
|
|
for (rr_sect = HIO_DNS_RR_PART_ANSWER; rr_sect <= HIO_DNS_RR_PART_ADDITIONAL;)
|
|
{
|
|
hio_oow_t match_count = 0;
|
|
for (i = 0; i < rr_count; i++)
|
|
{
|
|
if (rr[i].part == rr_sect)
|
|
{
|
|
hio_uint16_t rrdata_len;
|
|
|
|
dnlen = to_dn(rr[i].rrname, dn);
|
|
HIO_ASSERT (hio, dnlen > 0);
|
|
|
|
rrtr = (hio_dns_rrtr_t*)(dn + dnlen);
|
|
rrtr->rrtype = hio_hton16(rr[i].rrtype);
|
|
rrtr->rrclass = hio_hton16(rr[i].rrclass);
|
|
rrtr->ttl = hio_hton32(rr[i].ttl);
|
|
|
|
encode_rrdata_in_dns_msg(hio, &rr[i], &rrdata_len, rrtr + 1); /* this must succeed */
|
|
rrtr->dlen = hio_hton16(rrdata_len);
|
|
dn = (hio_uint8_t*)(rrtr + 1) + rrdata_len;
|
|
|
|
match_count++;
|
|
}
|
|
}
|
|
|
|
rr_sect = rr_sect + 1;
|
|
((hio_dns_pkt_alt_t*)pkt)->rrcount[rr_sect] = hio_hton16(match_count);
|
|
}
|
|
|
|
if (edns)
|
|
{
|
|
hio_dns_eopt_t* eopt;
|
|
hio_dns_beopt_t* beopt;
|
|
|
|
/* add EDNS0 OPT RR */
|
|
*dn = 0; /* root domain. as if to_dn("") is called */
|
|
rrtr = (hio_dns_rrtr_t*)(dn + 1);
|
|
rrtr->rrtype = HIO_CONST_HTON16(HIO_DNS_RRT_OPT);
|
|
rrtr->rrclass = hio_hton16(edns->uplen);
|
|
rrtr->ttl = hio_hton32(HIO_DNS_EDNS_MAKE_TTL(bhdr->rcode, edns->version, edns->dnssecok));
|
|
rrtr->dlen = hio_hton16((hio_uint16_t)edns_dlen);
|
|
dn = (hio_uint8_t*)(rrtr + 1);
|
|
|
|
beopt = edns->beoptr;
|
|
eopt = (hio_dns_eopt_t*)dn;
|
|
msg->ednsrrtroff = (hio_uint8_t*)rrtr - (hio_uint8_t*)pkt;
|
|
|
|
for (i = 0; i < edns->beonum; i++)
|
|
{
|
|
eopt->code = hio_hton16(beopt->code);
|
|
eopt->dlen = hio_hton16(beopt->dlen);
|
|
HIO_MEMCPY (++eopt, beopt->dptr, beopt->dlen);
|
|
eopt = (hio_dns_eopt_t*)((hio_uint8_t*)eopt + beopt->dlen);
|
|
beopt++;
|
|
}
|
|
|
|
pkt->arcount = hio_hton16((hio_ntoh16(pkt->arcount) + 1));
|
|
dn += edns_dlen;
|
|
}
|
|
|
|
pkt->qdcount = hio_hton16(qr_count);
|
|
pkt->id = hio_hton16((hio_uint16_t)bhdr->id);
|
|
|
|
/*pkt->qr = (rr_count > 0);
|
|
pkt->opcode = HIO_DNS_OPCODE_QUERY;*/
|
|
pkt->qr = bhdr->qr & 0x01;
|
|
pkt->opcode = bhdr->opcode & 0x0F;
|
|
pkt->aa = bhdr->aa & 0x01;
|
|
pkt->tc = bhdr->tc & 0x01;
|
|
pkt->rd = bhdr->rd & 0x01;
|
|
pkt->ra = bhdr->ra & 0x01;
|
|
pkt->ad = bhdr->ad & 0x01;
|
|
pkt->cd = bhdr->cd & 0x01;
|
|
pkt->rcode = bhdr->rcode & 0x0F;
|
|
|
|
msg->pktlen = dn - (hio_uint8_t*)pkt;
|
|
HIO_ASSERT (hio, msg->pktlen == pktlen);
|
|
HIO_ASSERT (hio, msg->pktalilen == HIO_ALIGN_POW2(pktlen, HIO_SIZEOF_VOID_P));
|
|
|
|
return msg;
|
|
}
|
|
|
|
void hio_dns_free_msg (hio_t* hio, hio_dns_msg_t* msg)
|
|
{
|
|
/* TODO: better management */
|
|
hio_freemem (hio, msg);
|
|
}
|
|
|
|
hio_uint8_t* hio_dns_find_client_cookie_in_msg (hio_dns_msg_t* reqmsg, hio_uint8_t (*cookie)[HIO_DNS_COOKIE_CLIENT_LEN])
|
|
{
|
|
hio_dns_rrtr_t* edns_rrtr;
|
|
hio_dns_eopt_t* eopt;
|
|
hio_uint16_t rem, dlen;
|
|
|
|
/* this function doesn't check malformed packet assuming
|
|
* reqmsg points to the packet message created with hio_dns_make_msg().
|
|
* such a packet message must be well-formed */
|
|
if (reqmsg->ednsrrtroff <= 0) return HIO_NULL; /* doesn't exist */
|
|
|
|
edns_rrtr = (hio_dns_rrtr_t*)((hio_uint8_t*)hio_dns_msg_to_pkt(reqmsg) + reqmsg->ednsrrtroff);
|
|
rem = hio_ntoh16(edns_rrtr->dlen);
|
|
|
|
eopt = (hio_dns_eopt_t*)(edns_rrtr + 1);
|
|
while (rem >= HIO_SIZEOF(hio_dns_eopt_t))
|
|
{
|
|
dlen = hio_ntoh16(eopt->dlen);
|
|
if (eopt->code == HIO_CONST_HTON16(HIO_DNS_EOPT_COOKIE))
|
|
{
|
|
if (cookie) HIO_MEMCPY (cookie, eopt + 1, HIO_DNS_COOKIE_CLIENT_LEN);
|
|
return (hio_uint8_t*)(eopt + 1);
|
|
}
|
|
|
|
rem -= dlen;
|
|
eopt = (hio_dns_eopt_t*)((hio_uint8_t*)(eopt + 1) + dlen);
|
|
}
|
|
|
|
return HIO_NULL;
|
|
}
|
|
|
|
|
|
hio_bch_t* hio_dns_rcode_to_bcstr (hio_dns_rcode_t rcode)
|
|
{
|
|
hio_bch_t* _errmsg[] =
|
|
{
|
|
"NOERR",
|
|
"FORMERR",
|
|
"SERVFAIL",
|
|
"NXDOMAIN",
|
|
"NOTIMPL",
|
|
"REFUSED",
|
|
"YXDOMAIN",
|
|
"YXRRSET",
|
|
"NXRRSET",
|
|
"NOAUTH",
|
|
"NOTZONE", /* 10 */
|
|
|
|
"UNKNOWNERR",
|
|
"UNKNOWNERR",
|
|
"UNKNOWNERR",
|
|
"UNKNOWNERR",
|
|
"UNKNOWNERR",
|
|
"UNKNOWNERR",
|
|
|
|
"BADVERS", /* 16 */
|
|
"BADSIG",
|
|
"BADTIME",
|
|
"BADMODE",
|
|
"BADNAME",
|
|
"BADALG",
|
|
"BADTRUNC",
|
|
"BADCOOKIE"
|
|
};
|
|
|
|
return rcode < HIO_COUNTOF(_errmsg)? _errmsg[rcode]: "UNKNOWNERR";
|
|
}
|