From 62775ec4b34bd2a1b5e4c60aa40ba67bd979069a Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 9 May 2023 19:21:45 +0200 Subject: nslookup: ensure unique transaction IDs for the DNS queries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Based on patch by Uwe Kleine-König. It makes sense to actually see the nitty-gritty details of DNS querying, so bringing in (commented-out) musl's DNS request code. function old new delta nslookup_main 760 822 +62 Signed-off-by: Denys Vlasenko --- networking/nslookup.c | 394 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 390 insertions(+), 4 deletions(-) diff --git a/networking/nslookup.c b/networking/nslookup.c index 6da97baf4..24eae4010 100644 --- a/networking/nslookup.c +++ b/networking/nslookup.c @@ -34,16 +34,14 @@ //usage: "Name: debian\n" //usage: "Address: 127.0.0.1\n" +#if !ENABLE_FEATURE_NSLOOKUP_BIG + #include -#include /* for IFNAMSIZ */ //#include //#include #include "libbb.h" #include "common_bufsiz.h" - -#if !ENABLE_FEATURE_NSLOOKUP_BIG - /* * Mini nslookup implementation for busybox * @@ -248,12 +246,385 @@ int nslookup_main(int argc, char **argv) * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include "libbb.h" +#include "common_bufsiz.h" + #if 0 # define dbg(...) fprintf(stderr, __VA_ARGS__) #else # define dbg(...) ((void)0) #endif +/* Instead of using ancient libc DNS query support, + * we can carry our own, independent code. + * E.g. res_mkquery() loses + * three of its paramemters (they are unused!). + * Unfortunately, while it does eliminate + * ns_get16 + * ns_get32 + * ns_name_uncompress + * dn_skipname + * ns_skiprr + * ns_initparse + * ns_parserr + * libc functions from a static binary, libc versions of + * dn_expand and res_mkquery are still linked in + * - they are used by getnameinfo(). Each is ~230 bytes of code. + * This makes USE_LIBC_RESOLV = 0 code _bigger_ (by about 27 bytes), + * despite inlining and constant propagation. + */ +#define USE_LIBC_RESOLV 1 + +#if USE_LIBC_RESOLV + +#include + +#else + +#define RESOLVFUNC /*nothing*/ +#define BIGRESOLVFUNC /*nothing*/ +#define TINYRESOLVFUNC ALWAYS_INLINE + +/* This one is taken from musl 1.2.4 */ + +#define NS_MAXDNAME 1025 +#define NS_INT32SZ 4 +#define NS_INT16SZ 2 + +#define MAXDNAME NS_MAXDNAME + +typedef enum __ns_opcode { + ns_o_query = 0, +} ns_opcode; +typedef enum __ns_class { + ns_c_in = 1, +} ns_class; +typedef enum __ns_sect { + ns_s_qd = 0, + ns_s_zn = 0, + ns_s_an = 1, + ns_s_pr = 1, + ns_s_ns = 2, + ns_s_ud = 2, + ns_s_ar = 3, + ns_s_max = 4 +} ns_sect; +typedef enum __ns_type { + ns_t_a = 1, + ns_t_ns = 2, + ns_t_cname = 5, + ns_t_soa = 6, + ns_t_ptr = 12, + ns_t_mx = 15, + ns_t_txt = 16, + ns_t_aaaa = 28, + ns_t_srv = 33, + ns_t_any = 255, +} ns_type; +#define QUERY ns_o_query +#define T_A ns_t_a +#define T_PTR ns_t_ptr +#define T_AAAA ns_t_aaaa +#define C_IN ns_c_in + +typedef struct __ns_msg { + const unsigned char *_msg, *_eom; + uint16_t _id, _flags, _counts[ns_s_max]; + const unsigned char *_sections[ns_s_max]; + ns_sect _sect; + int _rrnum; + const unsigned char *_msg_ptr; +} ns_msg; +#define ns_msg_id(handle) ((handle)._id + 0) +#define ns_msg_base(handle) ((handle)._msg + 0) +#define ns_msg_end(handle) ((handle)._eom + 0) +#define ns_msg_size(handle) ((handle)._eom - (handle)._msg) +#define ns_msg_count(handle, section) ((handle)._counts[section] + 0) +#define ns_msg_getflag(handle, flag) \ + (((handle)._flags & _ns_flagdata[flag].mask) >> _ns_flagdata[flag].shift) + +typedef struct __ns_rr { + char name[NS_MAXDNAME]; + uint16_t type; + uint16_t rr_class; + uint32_t ttl; + uint16_t rdlength; + const unsigned char *rdata; +} ns_rr; +#define ns_rr_name(rr) (((rr).name[0] != '\0') ? (rr).name : ".") +#define ns_rr_type(rr) ((ns_type)((rr).type + 0)) +#define ns_rr_class(rr) ((ns_class)((rr).rr_class + 0)) +#define ns_rr_ttl(rr) ((rr).ttl + 0) +#define ns_rr_rdlen(rr) ((rr).rdlength + 0) +#define ns_rr_rdata(rr) ((rr).rdata + 0) + +typedef struct { + unsigned id :16; +#if __BYTE_ORDER == __BIG_ENDIAN + unsigned qr: 1; + unsigned opcode: 4; + unsigned aa: 1; + unsigned tc: 1; + unsigned rd: 1; + unsigned ra: 1; + unsigned unused :1; + unsigned ad: 1; + unsigned cd: 1; + unsigned rcode :4; +#else + unsigned rd :1; + unsigned tc :1; + unsigned aa :1; + unsigned opcode :4; + unsigned qr :1; + unsigned rcode :4; + unsigned cd: 1; + unsigned ad: 1; + unsigned unused :1; + unsigned ra :1; +#endif + unsigned qdcount :16; + unsigned ancount :16; + unsigned nscount :16; + unsigned arcount :16; +} HEADER; + +#define dn_ns_get16 bb_ns_get16 +static unsigned TINYRESOLVFUNC ns_get16(const unsigned char *cp) +{ + return cp[0]<<8 | cp[1]; +} +#define ns_get32 bb_ns_get32 +static unsigned long TINYRESOLVFUNC ns_get32(const unsigned char *cp) +{ + return (unsigned)cp[0]<<24 | cp[1]<<16 | cp[2]<<8 | cp[3]; +} +#define NS_GET16(s, cp) (void)((s) = ns_get16(((cp)+=2)-2)) +#define NS_GET32(l, cp) (void)((l) = ns_get32(((cp)+=4)-4)) + +#define dn_expand bb_dn_expand +static int BIGRESOLVFUNC dn_expand(const unsigned char *base, const unsigned char *end, const unsigned char *src, char *dest, int space) +{ + const unsigned char *p = src; + char *dend, *dbegin = dest; + int len = -1, i, j; + if (p==end || space <= 0) return -1; + dend = dest + (space > 254 ? 254 : space); + /* detect reference loop using an iteration counter */ + for (i=0; i < end-base; i+=2) { + /* loop invariants: p= end-base) return -1; + p = base+j; + } else if (*p) { + if (dest != dbegin) *dest++ = '.'; + j = *p++; + if (j >= end-p || j >= dend-dest) return -1; + while (j--) *dest++ = *p++; + } else { + *dest = 0; + if (len < 0) len = p+1-src; + return len; + } + } + return -1; +} + +#define ns_name_uncompress bb_ns_name_uncompress +static int RESOLVFUNC ns_name_uncompress(const unsigned char *msg, const unsigned char *eom, + const unsigned char *src, char *dst, size_t dstsiz) +{ + int r; + r = dn_expand(msg, eom, src, dst, dstsiz); + if (r < 0) errno = EMSGSIZE; + return r; +} + +#define dn_skipname bb_dn_skipname +static int RESOLVFUNC dn_skipname(const unsigned char *s, const unsigned char *end) +{ + const unsigned char *p = s; + while (p < end) + if (!*p) return p-s+1; + else if (*p>=192) + if (p+1 eom - p) goto bad; + p += r + 2 * NS_INT16SZ; + if (section != ns_s_qd) { + if (NS_INT32SZ + NS_INT16SZ > eom - p) goto bad; + p += NS_INT32SZ; + NS_GET16(r, p); + if (r > eom - p) goto bad; + p += r; + } + } + return p - ptr; +bad: + errno = EMSGSIZE; + return -1; +} + +#define ns_parserr bb_ns_parserr +static int BIGRESOLVFUNC ns_parserr(ns_msg *handle, ns_sect section, int rrnum, ns_rr *rr) +{ + int r; + + if (section < 0 || section >= ns_s_max) goto bad; + if (section != handle->_sect) { + handle->_sect = section; + handle->_rrnum = 0; + handle->_msg_ptr = handle->_sections[section]; + } + if (rrnum == -1) rrnum = handle->_rrnum; + if (rrnum < 0 || rrnum >= handle->_counts[section]) goto bad; + if (rrnum < handle->_rrnum) { + handle->_rrnum = 0; + handle->_msg_ptr = handle->_sections[section]; + } + if (rrnum > handle->_rrnum) { + r = ns_skiprr(handle->_msg_ptr, handle->_eom, section, rrnum - handle->_rrnum); + if (r < 0) return -1; + handle->_msg_ptr += r; + handle->_rrnum = rrnum; + } + r = ns_name_uncompress(handle->_msg, handle->_eom, handle->_msg_ptr, rr->name, NS_MAXDNAME); + if (r < 0) return -1; + handle->_msg_ptr += r; + if (2 * NS_INT16SZ > handle->_eom - handle->_msg_ptr) goto size; + NS_GET16(rr->type, handle->_msg_ptr); + NS_GET16(rr->rr_class, handle->_msg_ptr); + if (section != ns_s_qd) { + if (NS_INT32SZ + NS_INT16SZ > handle->_eom - handle->_msg_ptr) goto size; + NS_GET32(rr->ttl, handle->_msg_ptr); + NS_GET16(rr->rdlength, handle->_msg_ptr); + if (rr->rdlength > handle->_eom - handle->_msg_ptr) goto size; + rr->rdata = handle->_msg_ptr; + handle->_msg_ptr += rr->rdlength; + } else { + rr->ttl = 0; + rr->rdlength = 0; + rr->rdata = NULL; + } + handle->_rrnum++; + if (handle->_rrnum > handle->_counts[section]) { + handle->_sect = section + 1; + if (handle->_sect == ns_s_max) { + handle->_rrnum = -1; + handle->_msg_ptr = NULL; + } else { + handle->_rrnum = 0; + } + } + return 0; +bad: + errno = ENODEV; + return -1; +size: + errno = EMSGSIZE; + return -1; +} + +#define ns_initparse bb_ns_initparse +static int BIGRESOLVFUNC ns_initparse(const unsigned char *msg, int msglen, ns_msg *handle) +{ + int i, r; + + handle->_msg = msg; + handle->_eom = msg + msglen; + if (msglen < (2 + ns_s_max) * NS_INT16SZ) goto bad; + NS_GET16(handle->_id, msg); + NS_GET16(handle->_flags, msg); + for (i = 0; i < ns_s_max; i++) NS_GET16(handle->_counts[i], msg); + for (i = 0; i < ns_s_max; i++) { + if (handle->_counts[i]) { + handle->_sections[i] = msg; + r = ns_skiprr(msg, handle->_eom, i, handle->_counts[i]); + if (r < 0) return -1; + msg += r; + } else { + handle->_sections[i] = NULL; + } + } + if (msg != handle->_eom) goto bad; + handle->_sect = ns_s_max; + handle->_rrnum = -1; + handle->_msg_ptr = NULL; + return 0; +bad: + errno = EMSGSIZE; + return -1; +} + +#define res_mkquery bb_res_mkquery +static int RESOLVFUNC res_mkquery(int op, const char *dname, int class, int type, + const unsigned char *data UNUSED_PARAM, int datalen UNUSED_PARAM, + const unsigned char *newrr UNUSED_PARAM, unsigned char *buf, int buflen) +{ + int i, j; + unsigned char q[280]; + size_t l = strnlen(dname, 255); + int n; + + if (l && dname[l-1]=='.') l--; + if (l && dname[l-1]=='.') return -1; + n = 17+l+!!l; + if (l>253 || buflen15u || class>255u || type>255u) + return -1; + + /* Construct query template - ID will be filled later */ + memset(q, 0, n); + q[2] = op*8 + 1; + q[3] = 32; /* AD */ + q[5] = 1; + memcpy((char *)q+13, dname, l); + for (i=13; q[i]; i=j+1) { + for (j=i; q[j] && q[j] != '.'; j++); + if (j-i-1u > 62u) return -1; + q[i-1] = j-i; + } + q[i+1] = type; + q[i+3] = class; +#if 0 +//For some machines (here: a TP-Link RE200 powered by a MediaTek MT7620A) +//the monotonic clock has a coarse resolution (here: 20us) and it can happen +//that the requests for A and AAAA share the same transaction ID. + +//In that case the mapping from received responses to the sent queries +//doesn't work and name resolution fails because the AAAA reply +//is dropped as a duplicate reply to the A query. + /* Make a reasonably unpredictable id */ + unsigned id; + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + id = ts.tv_nsec + ((uint32_t)(ts.tv_nsec) >> 16); + q[0] = id/256; + q[1] = id; +#endif + memcpy(buf, q, n); + return n; +} + +#endif /* !USE_LIBC_RESOLV */ + + struct ns { const char *name; len_and_sockaddr *lsa; @@ -978,6 +1349,21 @@ int nslookup_main(int argc UNUSED_PARAM, char **argv) } } + /* Ensure the Transaction IDs are unique. + * See, for example, musl source of res_mkquery() where + * it risks using current time (same value!) for ALL queries. + */ + { + struct timeval tv; + unsigned id; + xgettimeofday(&tv); + id = tv.tv_sec + tv.tv_usec; + for (rc = 0; rc < G.query_count; rc++) { + G.query[rc].query[0] = id >> 8; + G.query[rc].query[1] = id++; + } + } + for (rc = 0; rc < G.serv_count;) { int c; -- cgit v1.2.1