From 956b7ebf685c51442c9723767b7a85697ffbaf47 Mon Sep 17 00:00:00 2001 From: Brad House Date: Sat, 11 Nov 2023 09:32:32 -0500 Subject: [PATCH] rewrite adig using new helpers (#607) adig previously performed manual parsing of the DNS records. Now it can focus strictly on formatting of output data for printing. It simply iterates across the parsed DNS packet and queries for the RRs, parameters for each RR, and the datatypes for each parameter. adig will now automatically pick up new RRs from the c-ares library due to the dynamic nature. The adig format also now more closely resembles that of BIND's `dig` output. A few more helpers needed to be added to the c-ares library that were missing. There ware a couple of minor bugs and enhancements also needed. Example: ``` ./adig -t ANY www.google.com ; <<>> c-ares DiG 1.21.0 <<>> www.google.com ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: RCODE, id: 23913 ;; flags: qr rd ra; QUERY: 1, ANSWER: 11, AUTHORITY: 0, ADDITIONAL: 1 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags: 0; udp: 512 ;; QUESTION SECTION: ;www.google.com. IN ANY ;; ANSWER SECTION: www.google.com. 162 IN A 142.251.107.99 www.google.com. 162 IN A 142.251.107.105 www.google.com. 162 IN A 142.251.107.103 www.google.com. 162 IN A 142.251.107.147 www.google.com. 162 IN A 142.251.107.104 www.google.com. 162 IN A 142.251.107.106 www.google.com. 162 IN AAAA 2607:f8b0:400c:c32::93 www.google.com. 162 IN AAAA 2607:f8b0:400c:c32::69 www.google.com. 162 IN AAAA 2607:f8b0:400c:c32::68 www.google.com. 162 IN AAAA 2607:f8b0:400c:c32::6a www.google.com. 21462 IN HTTPS 1 . alpn="h2,h3" ;; MSG SIZE rcvd: 276 ``` Fix By: Brad House (@bradh352) --- docs/adig.1 | 26 +- include/ares.h | 10 + include/ares_dns_record.h | 198 ++++- src/lib/ares__buf.c | 33 +- src/lib/ares__buf.h | 20 + src/lib/ares__hosts_file.c | 10 +- src/lib/ares_dns_mapping.c | 313 ++++++- src/lib/ares_dns_record.c | 72 +- src/lib/ares_gethostbyaddr.c | 40 +- src/lib/ares_math.c | 45 + src/lib/ares_private.h | 15 +- src/lib/ares_process.c | 10 +- src/lib/ares_query.c | 24 +- src/lib/ares_send.c | 33 +- src/tools/adig.c | 1541 +++++++++++++++------------------- src/tools/ahost.c | 16 +- src/tools/ares_getopt.c | 74 +- src/tools/ares_getopt.h | 38 +- test/ares-test-internal.cc | 21 +- 19 files changed, 1486 insertions(+), 1053 deletions(-) diff --git a/docs/adig.1 b/docs/adig.1 index 6760bbef..38947166 100644 --- a/docs/adig.1 +++ b/docs/adig.1 @@ -44,12 +44,13 @@ Print some extra debugging output. \fB\-f\fR flag Add a behavior control flag. Possible values for flag are - igntc - ignore to query in TCP to get truncated UDP answer, + igntc - ignore query truncation, return answer as-is instead of retrying + via tcp. noaliases - don't honor the HOSTALIASES environment variable, norecurse - don't query upstream servers recursively, - primary - use the first server, - stayopen - don't close the communication sockets, and - usevc - always use TCP. + primary - use the first server, + stayopen - don't close the communication sockets, and + usevc - always use TCP. .TP \fB\-h\fR, \fB\-?\fR Display this help and exit. @@ -61,27 +62,14 @@ Servers are tried in round-robin, if the previous one failed. \fB\-t\fR type Query records of specified type. Possible values for type are -A (default), AAAA, AFSDB, ANY, AXFR, CNAME, GPOS, HINFO, ISDN, KEY, LOC, MAILA, -MAILB, MB, MD, MF, MG, MINFO, MR, MX, NAPTR, NS, NSAP, NSAP_PTR, NULL, -PTR, PX, RP, RT, SIG, SOA, SRV, TXT, URI, WKS and X25. +A (default), AAAA, ANY, AXFR, CNAME, HINFO, MX, NAPTR, NS, PTR, SOA, SRV, TXT, +URI, CAA, SVCB, and HTTPS. .TP \fB\-T\fR port Connect to the specified TCP port of DNS server. .TP \fB\-U\fR port Connect to the specified UDP port of DNS server. -.TP -\fB\-x\fR -For an IPv4 \fB-t PTR a.b.c.d\fR lookup, query for -.br -\fBd.c.b.a.in-addr.arpa.\fR -This more often gives correct names in the \fBANSWER\fR. -.br -For an IPv6 \fB-t PTR addr\fR lookup, query for \fBa.b.c....z.IP6.ARPA.\fR -.TP -\fB\-xx\fR -As for \fB-x\fR and an IPv6 address, compact \fBa.b.c....z.IP6.ARPA.\fR into a RFC-2673 bit-string. -This compacted \fBbit-string\fR form is not supported by many DNS-servers. .SH "REPORTING BUGS" Report bugs to the c-ares mailing list: diff --git a/include/ares.h b/include/ares.h index ee8e87e2..19b8faf5 100644 --- a/include/ares.h +++ b/include/ares.h @@ -506,6 +506,16 @@ struct ares_in6_addr { } _S6_un; }; +struct ares_addr { + int family; + + union { + struct in_addr addr4; + struct ares_in6_addr addr6; + } addr; +}; + + struct ares_addrttl { struct in_addr ipaddr; int ttl; diff --git a/include/ares_dns_record.h b/include/ares_dns_record.h index e840bf8f..7a74ef17 100644 --- a/include/ares_dns_record.h +++ b/include/ares_dns_record.h @@ -115,17 +115,17 @@ typedef enum { /*! DNS Response Codes from server */ typedef enum { ARES_RCODE_NOERROR = 0, /*!< Success */ - ARES_RCODE_FORMAT_ERROR = 1, /*!< Format error. The name server was unable + ARES_RCODE_FORMERR = 1, /*!< Format error. The name server was unable * to interpret the query. */ - ARES_RCODE_SERVER_FAILURE = 2, /*!< Server Failure. The name server was + ARES_RCODE_SERVFAIL = 2, /*!< Server Failure. The name server was * unable to process this query due to a * problem with the nameserver */ - ARES_RCODE_NAME_ERROR = 3, /*!< Name Error. Meaningful only for + ARES_RCODE_NXDOMAIN = 3, /*!< Name Error. Meaningful only for * responses from an authoritative name * server, this code signifies that the * domain name referenced in the query does * not exist. */ - ARES_RCODE_NOT_IMPLEMENTED = 4, /*!< Not implemented. The name server does + ARES_RCODE_NOTIMP = 4, /*!< Not implemented. The name server does * not support the requested kind of * query */ ARES_RCODE_REFUSED = 5, /*!< Refused. The name server refuses to @@ -143,6 +143,15 @@ typedef enum { ARES_RCODE_NOTZONE = 10, /*!< RFC 2136. A name used in the Prerequisite * or Update Section is not within the zone * denoted by the Zone Section. */ + ARES_RCODE_DSOTYPEI = 11, /*!< RFC 8409. DSO-TYPE Not implemented */ + ARES_RCODE_BADSIG = 16, /*!< RFC 8945. TSIG Signature Failure */ + ARES_RCODE_BADKEY = 17, /*!< RFC 8945. Key not recognized. */ + ARES_RCODE_BADTIME = 18, /*!< RFC 8945. Signature out of time window. */ + ARES_RCODE_BADMODE = 19, /*!< RFC 2930. Bad TKEY Mode */ + ARES_RCODE_BADNAME = 20, /*!< RFC 2930. Duplicate Key Name */ + ARES_RCODE_BADALG = 21, /*!< RFC 2930. Algorithm not supported */ + ARES_RCODE_BADTRUNC = 22, /*!< RFC 8945. Bad Truncation */ + ARES_RCODE_BADCOOKIE = 23, /*!< RVC 7973. Bad/missing Server Cookie */ } ares_dns_rcode_t; /*! Data types used */ @@ -152,13 +161,14 @@ typedef enum { ARES_DATATYPE_U8 = 3, /*!< 8bit unsigned integer */ ARES_DATATYPE_U16 = 4, /*!< 16bit unsigned integer */ ARES_DATATYPE_U32 = 5, /*!< 32bit unsigned integer */ - ARES_DATATYPE_STR = 6, /*!< Null-terminated string */ - ARES_DATATYPE_BIN = 7, /*!< Binary data */ - ARES_DATATYPE_BINP = 8, /*!< Officially defined as binary data, but likely + ARES_DATATYPE_NAME = 6, /*!< Null-terminated string of a domain name */ + ARES_DATATYPE_STR = 7, /*!< Null-terminated string */ + ARES_DATATYPE_BIN = 8, /*!< Binary data */ + ARES_DATATYPE_BINP = 9, /*!< Officially defined as binary data, but likely * printable. Guaranteed to have a NULL * terminator for convenience (not included in * length) */ - ARES_DATATYPE_OPT = 9, /*!< Array of options. 16bit identifier, BINP + ARES_DATATYPE_OPT = 10, /*!< Array of options. 16bit identifier, BIN * data. */ } ares_dns_datatype_t; @@ -168,13 +178,13 @@ typedef enum { typedef enum { /*! A Record. Address. Datatype: INADDR */ ARES_RR_A_ADDR = (ARES_REC_TYPE_A * 100) + 1, - /*! NS Record. Name. Datatype: STR */ + /*! NS Record. Name. Datatype: NAME */ ARES_RR_NS_NSDNAME = (ARES_REC_TYPE_NS * 100) + 1, - /*! CNAME Record. CName. Datatype: STR */ + /*! CNAME Record. CName. Datatype: NAME */ ARES_RR_CNAME_CNAME = (ARES_REC_TYPE_CNAME * 100) + 1, - /*! SOA Record. MNAME, Primary Source of Data. Datatype: STR */ + /*! SOA Record. MNAME, Primary Source of Data. Datatype: NAME */ ARES_RR_SOA_MNAME = (ARES_REC_TYPE_SOA * 100) + 1, - /*! SOA Record. RNAME, Mailbox of person responsible. Datatype: STR */ + /*! SOA Record. RNAME, Mailbox of person responsible. Datatype: NAME */ ARES_RR_SOA_RNAME = (ARES_REC_TYPE_SOA * 100) + 2, /*! SOA Record. Serial, version. Datatype: U32 */ ARES_RR_SOA_SERIAL = (ARES_REC_TYPE_SOA * 100) + 3, @@ -186,7 +196,7 @@ typedef enum { ARES_RR_SOA_EXPIRE = (ARES_REC_TYPE_SOA * 100) + 6, /*! SOA Record. Minimum, RR TTL. Datatype: U32 */ ARES_RR_SOA_MINIMUM = (ARES_REC_TYPE_SOA * 100) + 7, - /*! PTR Record. DNAME, pointer domain. Datatype: STR */ + /*! PTR Record. DNAME, pointer domain. Datatype: NAME */ ARES_RR_PTR_DNAME = (ARES_REC_TYPE_PTR * 100) + 1, /*! HINFO Record. CPU. Datatype: STR */ ARES_RR_HINFO_CPU = (ARES_REC_TYPE_HINFO * 100) + 1, @@ -194,7 +204,7 @@ typedef enum { ARES_RR_HINFO_OS = (ARES_REC_TYPE_HINFO * 100) + 2, /*! MX Record. Preference. Datatype: U16 */ ARES_RR_MX_PREFERENCE = (ARES_REC_TYPE_MX * 100) + 1, - /*! MX Record. Exchange, domain. Datatype: STR */ + /*! MX Record. Exchange, domain. Datatype: NAME */ ARES_RR_MX_EXCHANGE = (ARES_REC_TYPE_MX * 100) + 2, /*! TXT Record. Data. Datatype: BINP */ ARES_RR_TXT_DATA = (ARES_REC_TYPE_TXT * 100) + 1, @@ -206,7 +216,7 @@ typedef enum { ARES_RR_SRV_WEIGHT = (ARES_REC_TYPE_SRV * 100) + 3, /*! SRV Record. Port. Datatype: U16 */ ARES_RR_SRV_PORT = (ARES_REC_TYPE_SRV * 100) + 4, - /*! SRV Record. Target domain. Datatype: STR */ + /*! SRV Record. Target domain. Datatype: NAME */ ARES_RR_SRV_TARGET = (ARES_REC_TYPE_SRV * 100) + 5, /*! NAPTR Record. Order. Datatype: U16 */ ARES_RR_NAPTR_ORDER = (ARES_REC_TYPE_NAPTR * 100) + 1, @@ -218,7 +228,7 @@ typedef enum { ARES_RR_NAPTR_SERVICES = (ARES_REC_TYPE_NAPTR * 100) + 4, /*! NAPTR Record. Regexp. Datatype: STR */ ARES_RR_NAPTR_REGEXP = (ARES_REC_TYPE_NAPTR * 100) + 5, - /*! NAPTR Record. Replacement. Datatype: STR */ + /*! NAPTR Record. Replacement. Datatype: NAME */ ARES_RR_NAPTR_REPLACEMENT = (ARES_REC_TYPE_NAPTR * 100) + 6, /*! OPT Record. UDP Size. Datatype: U16 */ ARES_RR_OPT_UDP_SIZE = (ARES_REC_TYPE_OPT * 100) + 1, @@ -240,13 +250,13 @@ typedef enum { ARES_RR_TLSA_DATA = (ARES_REC_TYPE_TLSA * 100) + 4, /*! SVCB Record. SvcPriority. Datatype: U16 */ ARES_RR_SVCB_PRIORITY = (ARES_REC_TYPE_SVCB * 100) + 1, - /*! SVCB Record. TargetName. Datatype: STR */ + /*! SVCB Record. TargetName. Datatype: NAME */ ARES_RR_SVCB_TARGET = (ARES_REC_TYPE_SVCB * 100) + 2, /*! SVCB Record. SvcParams. Datatype: OPT */ ARES_RR_SVCB_PARAMS = (ARES_REC_TYPE_SVCB * 100) + 3, /*! HTTPS Record. SvcPriority. Datatype: U16 */ ARES_RR_HTTPS_PRIORITY = (ARES_REC_TYPE_HTTPS * 100) + 1, - /*! HTTPS Record. TargetName. Datatype: STR */ + /*! HTTPS Record. TargetName. Datatype: NAME */ ARES_RR_HTTPS_TARGET = (ARES_REC_TYPE_HTTPS * 100) + 2, /*! HTTPS Record. SvcParams. Datatype: OPT */ ARES_RR_HTTPS_PARAMS = (ARES_REC_TYPE_HTTPS * 100) + 3, @@ -254,7 +264,7 @@ typedef enum { ARES_RR_URI_PRIORITY = (ARES_REC_TYPE_URI * 100) + 1, /*! URI Record. Weight. Datatype: U16 */ ARES_RR_URI_WEIGHT = (ARES_REC_TYPE_URI * 100) + 2, - /*! URI Record. Target domain. Datatype: STR */ + /*! URI Record. Target domain. Datatype: NAME */ ARES_RR_URI_TARGET = (ARES_REC_TYPE_URI * 100) + 3, /*! CAA Record. Critical flag. Datatype: U8 */ ARES_RR_CAA_CRITICAL = (ARES_REC_TYPE_CAA * 100) + 1, @@ -298,7 +308,7 @@ typedef enum { ARES_TLSA_MATCH_SHA512 = 2 } ares_tlsa_match_t; -/*! SVCB (and HTTPS) known parameters */ +/*! SVCB (and HTTPS) RR known parameters */ typedef enum { /*! Mandatory keys in this RR (RFC 9460 Section 8) */ ARES_SVCB_PARAM_MANDATORY = 0, @@ -316,6 +326,66 @@ typedef enum { ARES_SVCB_PARAM_IPV6HINT = 6 } ares_svcb_param_t; +/*! OPT RR known parameters */ +typedef enum { + /*! RFC 8764. Apple's DNS Long-Lived Queries Protocol */ + ARES_OPT_PARAM_LLQ = 1, + /*! http://files.dns-sd.org/draft-sekar-dns-ul.txt: Update Lease */ + ARES_OPT_PARAM_UL = 2, + /*! RFC 5001. Name Server Identification */ + ARES_OPT_PARAM_NSID = 3, + /*! RFC 6975. DNSSEC Algorithm Understood */ + ARES_OPT_PARAM_DAU = 5, + /*! RFC 6975. DS Hash Understood */ + ARES_OPT_PARAM_DHU = 6, + /*! RFC 6975. NSEC3 Hash Understood */ + ARES_OPT_PARAM_N3U = 7, + /*! RFC 7871. Client Subnet */ + ARES_OPT_PARAM_EDNS_CLIENT_SUBNET = 8, + /*! RFC 7314. Expire Timer */ + ARES_OPT_PARAM_EDNS_EXPIRE = 9, + /*! RFC 7873. Client and Server Cookies */ + ARES_OPT_PARAM_COOKIE = 10, + /*! RFC 7828. TCP Keepalive timeout */ + ARES_OPT_PARAM_EDNS_TCP_KEEPALIVE = 11, + /*! RFC 7830. Padding */ + ARES_OPT_PARAM_PADDING = 12, + /*! RFC 7901. Chain query requests */ + ARES_OPT_PARAM_CHAIN = 13, + /*! RFC 8145. Signaling Trust Anchor Knowledge in DNSSEC */ + ARES_OPT_PARAM_EDNS_KEY_TAG = 14, + /*! RFC 8914. Extended ERROR code and message */ + ARES_OPT_PARAM_EXTENDED_DNS_ERROR = 15, +} ares_opt_param_t; + +/*! Data type for option records for keys like ARES_RR_OPT_OPTIONS and + * ARES_RR_HTTPS_PARAMS returned by ares_dns_opt_get_datatype() */ +typedef enum { + /*! No value allowed for this option */ + ARES_OPT_DATATYPE_NONE = 1, + /*! List of strings, each prefixed with a single octet representing the length + */ + ARES_OPT_DATATYPE_STR_LIST = 2, + /*! List of 8bit integers, concatenated */ + ARES_OPT_DATATYPE_U8_LIST = 3, + /*! 16bit integer in network byte order */ + ARES_OPT_DATATYPE_U16 = 4, + /*! list of 16bit integer in network byte order, concatenated. */ + ARES_OPT_DATATYPE_U16_LIST = 5, + /*! 32bit integer in network byte order */ + ARES_OPT_DATATYPE_U32 = 6, + /*! list 32bit integer in network byte order, concatenated */ + ARES_OPT_DATATYPE_U32_LIST = 7, + /*! List of ipv4 addresses in network byte order, concatenated */ + ARES_OPT_DATATYPE_INADDR4_LIST = 8, + /*! List of ipv6 addresses in network byte order, concatenated */ + ARES_OPT_DATATYPE_INADDR6_LIST = 9, + /*! Binary Data */ + ARES_OPT_DATATYPE_BIN = 10, + /*! DNS Domain Name Format */ + ARES_OPT_DATATYPE_NAME = 11 +} ares_dns_opt_datatype_t; + /*! String representation of DNS Record Type * * \param[in] type DNS Record Type @@ -344,6 +414,81 @@ CARES_EXTERN const char *ares_dns_opcode_tostr(ares_dns_opcode_t opcode); */ CARES_EXTERN const char *ares_dns_rr_key_tostr(ares_dns_rr_key_t key); +/*! String representation of DNS Resource Record section + * + * \param[in] section Section + * \return string + */ +CARES_EXTERN const char *ares_dns_section_tostr(ares_dns_section_t section); + +/*! Convert DNS class name as string to ares_dns_class_t + * + * \param[out] qclass Pointer passed by reference to write class + * \param[in] str String to convert + * \return ARES_TRUE on success + */ +CARES_EXTERN ares_bool_t ares_dns_class_fromstr(ares_dns_class_t *qclass, + const char *str); + +/*! Convert DNS record type as string to ares_dns_rec_type_t + * + * \param[out] qclass Pointer passed by reference to write record type + * \param[in] str String to convert + * \return ARES_TRUE on success + */ +CARES_EXTERN ares_bool_t ares_dns_rec_type_fromstr(ares_dns_rec_type_t *qtype, + const char *str); + + +/*! Convert DNS response code as string to from ares_dns_rcode_t + * + * \param[in] rcode Response code to convert + * \return ARES_TRUE on success + */ +CARES_EXTERN const char *ares_dns_rcode_tostr(ares_dns_rcode_t rcode); + +/*! Convert any valid ip address (ipv4 or ipv6) into struct ares_addr and + * return the starting pointer of the network byte order address and the + * length of the address (4 or 16). + * + * \param[in] ipaddr ASCII string form of the ip address + * \param[in,out] addr Must set "family" member to one of AF_UNSPEC, + * AF_INET, AF_INET6 on input. + * \param[out] ptr_len Length of binary form address + * \return Pointer to start of binary address or NULL on error. + */ +CARES_EXTERN const void *ares_dns_pton(const char *ipaddr, + struct ares_addr *addr, size_t *out_len); + +/*! Convert an ip address into the PTR format for in-addr.arpa or in6.arpa + * + * \param[in] addr properly filled address structure + * \return String representing PTR, use ares_free_string() to free + */ +CARES_EXTERN char *ares_dns_addr_to_ptr(const struct ares_addr *addr); + + +/*! The options/parameters extensions to some RRs can be somewhat opaque, this + * is a helper to return the best match for a datatype for interpreting the + * option record. + * + * \param[in] key Key associated with options/parameters + * \param[in] opt Option Key/Parameter + * \return Datatype + */ +CARES_EXTERN ares_dns_opt_datatype_t + ares_dns_opt_get_datatype(ares_dns_rr_key_t key, unsigned short opt); + +/*! The options/parameters extensions to some RRs can be somewhat opaque, this + * is a helper to return the name if the option is known. + * + * \param[in] key Key associated with options/parameters + * \param[in] opt Option Key/Parameter + * \return name, or NULL if not known. + */ +CARES_EXTERN const char *ares_dns_opt_get_name(ares_dns_rr_key_t key, + unsigned short opt); + /*! Opaque data type representing a DNS RR (Resource Record) */ struct ares_dns_rr; @@ -369,7 +514,8 @@ typedef struct ares_dns_record ares_dns_record_t; * \param[out] dnsrec Pointer passed by reference for a newly allocated * record object. Must be ares_dns_record_destroy()'d by * caller. - * \param[in] id DNS Query ID + * \param[in] id DNS Query ID. If structuring a new query to be sent + * with ares_send(), this value should be zero. * \param[in] flags DNS Flags from \ares_dns_flags_t * \param[in] opcode DNS OpCode (typically ARES_OPCODE_QUERY) * \param[in] rcode DNS RCode @@ -583,7 +729,7 @@ CARES_EXTERN ares_status_t const struct ares_in6_addr *addr); /*! Set string data for specified resource record and key. Can - * only be used on keys with datatype ARES_DATATYPE_STR + * only be used on keys with datatype ARES_DATATYPE_STR or ARES_DATATYPE_NAME. * * \param[in] dns_rr Pointer to resource record * \param[in] key DNS Resource Record Key @@ -680,7 +826,7 @@ CARES_EXTERN const struct ares_in6_addr * ares_dns_rr_get_addr6(const ares_dns_rr_t *dns_rr, ares_dns_rr_key_t key); /*! Retrieve a pointer to the string. Can only be used on keys with - * datatype ARES_DATATYPE_STR. + * datatype ARES_DATATYPE_STR and ARES_DATATYPE_NAME. * * \param[in] dns_rr Pointer to resource record * \param[in] key DNS Resource Record Key @@ -748,7 +894,8 @@ CARES_EXTERN size_t ares_dns_rr_get_opt_cnt(const ares_dns_rr_t *dns_rr, * \param[in] idx Index of option record * \param[out] val Optional. Pointer passed by reference to hold value. * Options may not have values. Value if returned is - * likely printable and guaranteed to be NULL terminated. + * guaranteed to be NULL terminated, however in most + * cases it is not printable. * \param[out] val_len Optional. Pointer passed by reference to hold value * length. * \return option key/id on success, 65535 on misuse. @@ -764,7 +911,8 @@ CARES_EXTERN unsigned short * \param[in] opt Option record key id (this is not the index). * \param[out] val Optional. Pointer passed by reference to hold value. * Options may not have values. Value if returned is - * likely printable and guaranteed to be NULL terminated. + * guaranteed to be NULL terminated, however in most cases + * it is not printable. * \param[out] val_len Optional. Pointer passed by reference to hold value * length. * \return ARES_TRUE on success, ARES_FALSE on misuse. diff --git a/src/lib/ares__buf.c b/src/lib/ares__buf.c index 0af1d668..1e928258 100644 --- a/src/lib/ares__buf.c +++ b/src/lib/ares__buf.c @@ -837,11 +837,40 @@ ares_status_t ares__buf_parse_dns_str(ares__buf_t *buf, size_t remaining_len, &len, allow_multiple); } -static ares_status_t ares__buf_append_num_hex(ares__buf_t *buf, size_t num, - size_t len) + +ares_status_t ares__buf_append_num_dec(ares__buf_t *buf, size_t num, size_t len) +{ + size_t i; + size_t mod; + + if (len == 0) { + len = ares__count_digits(num); + } + + mod = ares__pow(10, len); + + for (i=len; i>0; i--) { + size_t digit = (num % mod); + ares_status_t status; + + mod /= 10; + digit /= mod; + status = ares__buf_append_byte(buf, '0' + (unsigned char)(digit & 0xFF)); + if (status != ARES_SUCCESS) + return status; + } + return ARES_SUCCESS; +} + +ares_status_t ares__buf_append_num_hex(ares__buf_t *buf, size_t num, size_t len) { size_t i; static const unsigned char hexbytes[] = "0123456789ABCDEF"; + + if (len == 0) { + len = ares__count_hexdigits(num); + } + for (i = len; i > 0; i--) { ares_status_t status; status = ares__buf_append_byte(buf, hexbytes[(num >> ((i - 1) * 4)) & 0xF]); diff --git a/src/lib/ares__buf.h b/src/lib/ares__buf.h index ec77f9d4..9acb3213 100644 --- a/src/lib/ares__buf.h +++ b/src/lib/ares__buf.h @@ -106,6 +106,26 @@ ares_status_t ares__buf_append_be16(ares__buf_t *buf, unsigned short u16); */ ares_status_t ares__buf_append_be32(ares__buf_t *buf, unsigned int u32); +/*! Append a number in ASCII decimal form. + * + * \param[in] buf Initialized buffer object + * \param[in] num Number to print + * \param[in] len Length to output, use 0 for no padding + * \return ARES_SUCCESS on succeess + */ +ares_status_t ares__buf_append_num_dec(ares__buf_t *buf, size_t num, + size_t len); + +/*! Append a number in ASCII hexidecimal form. + * + * \param[in] buf Initialized buffer object + * \param[in] num Number to print + * \param[in] len Length to output, use 0 for no padding + * \return ARES_SUCCESS on succeess + */ +ares_status_t ares__buf_append_num_hex(ares__buf_t *buf, size_t num, + size_t len); + /*! Sets the current buffer length. This *may* be used if there is a need to * override a prior position in the buffer, such as if there is a length * prefix that isn't easily predictable, and you must go back and overwrite diff --git a/src/lib/ares__hosts_file.c b/src/lib/ares__hosts_file.c index 83705232..20ae0a98 100644 --- a/src/lib/ares__hosts_file.c +++ b/src/lib/ares__hosts_file.c @@ -179,8 +179,8 @@ static ares_bool_t ares__is_hostname(const char *str) return ARES_TRUE; } -static const void *ares__parse_ipaddr(const char *ipaddr, - struct ares_addr *addr, size_t *out_len) +const void *ares_dns_pton(const char *ipaddr, struct ares_addr *addr, + size_t *out_len) { const void *ptr = NULL; size_t ptr_len = 0; @@ -225,7 +225,7 @@ static ares_bool_t ares__normalize_ipaddr(const char *ipaddr, char *out, memset(&data, 0, sizeof(data)); data.family = AF_UNSPEC; - addr = ares__parse_ipaddr(ipaddr, &data, &addr_len); + addr = ares_dns_pton(ipaddr, &data, &addr_len); if (addr == NULL) { return ARES_FALSE; } @@ -894,7 +894,7 @@ ares_status_t ares__hosts_entry_to_hostent(const ares_hosts_entry_t *entry, memset(&addr, 0, sizeof(addr)); addr.family = family; - ptr = ares__parse_ipaddr(ipaddr, &addr, &ptr_len); + ptr = ares_dns_pton(ipaddr, &addr, &ptr_len); if (ptr == NULL) { continue; } @@ -1071,7 +1071,7 @@ ares_status_t ares__hosts_entry_to_addrinfo(const ares_hosts_entry_t *entry, memset(&addr, 0, sizeof(addr)); addr.family = family; - ptr = ares__parse_ipaddr(ipaddr, &addr, &ptr_len); + ptr = ares_dns_pton(ipaddr, &addr, &ptr_len); if (ptr == NULL) { continue; diff --git a/src/lib/ares_dns_mapping.c b/src/lib/ares_dns_mapping.c index 9726dbbc..ab8f52ef 100644 --- a/src/lib/ares_dns_mapping.c +++ b/src/lib/ares_dns_mapping.c @@ -44,16 +44,25 @@ ares_bool_t ares_dns_rcode_isvalid(ares_dns_rcode_t rcode) { switch (rcode) { case ARES_RCODE_NOERROR: - case ARES_RCODE_FORMAT_ERROR: - case ARES_RCODE_SERVER_FAILURE: - case ARES_RCODE_NAME_ERROR: - case ARES_RCODE_NOT_IMPLEMENTED: + case ARES_RCODE_FORMERR: + case ARES_RCODE_SERVFAIL: + case ARES_RCODE_NXDOMAIN: + case ARES_RCODE_NOTIMP: case ARES_RCODE_REFUSED: case ARES_RCODE_YXDOMAIN: case ARES_RCODE_YXRRSET: case ARES_RCODE_NXRRSET: case ARES_RCODE_NOTAUTH: case ARES_RCODE_NOTZONE: + case ARES_RCODE_DSOTYPEI: + case ARES_RCODE_BADSIG: + case ARES_RCODE_BADKEY: + case ARES_RCODE_BADTIME: + case ARES_RCODE_BADMODE: + case ARES_RCODE_BADNAME: + case ARES_RCODE_BADALG: + case ARES_RCODE_BADTRUNC: + case ARES_RCODE_BADCOOKIE: return ARES_TRUE; } return ARES_FALSE; @@ -413,17 +422,19 @@ ares_dns_datatype_t ares_dns_rr_key_datatype(ares_dns_rr_key_t key) case ARES_RR_SOA_MNAME: case ARES_RR_SOA_RNAME: case ARES_RR_PTR_DNAME: - case ARES_RR_HINFO_CPU: - case ARES_RR_HINFO_OS: case ARES_RR_MX_EXCHANGE: case ARES_RR_SRV_TARGET: case ARES_RR_SVCB_TARGET: case ARES_RR_HTTPS_TARGET: + case ARES_RR_NAPTR_REPLACEMENT: + case ARES_RR_URI_TARGET: + return ARES_DATATYPE_NAME; + + case ARES_RR_HINFO_CPU: + case ARES_RR_HINFO_OS: case ARES_RR_NAPTR_FLAGS: case ARES_RR_NAPTR_SERVICES: case ARES_RR_NAPTR_REGEXP: - case ARES_RR_NAPTR_REPLACEMENT: - case ARES_RR_URI_TARGET: case ARES_RR_CAA_TAG: return ARES_DATATYPE_STR; @@ -590,3 +601,289 @@ const ares_dns_rr_key_t *ares_dns_rr_get_keys(ares_dns_rec_type_t type, return NULL; } + +ares_bool_t ares_dns_class_fromstr(ares_dns_class_t *qclass, const char *str) +{ + size_t i; + + static const struct { + const char *name; + ares_dns_class_t qclass; + } list[] = { + {"IN", ARES_CLASS_IN }, + { "CH", ARES_CLASS_CHAOS }, + { "HS", ARES_CLASS_HESOID}, + { "NONE", ARES_CLASS_NONE }, + { "ANY", ARES_CLASS_ANY }, + { NULL, 0 } + }; + + if (qclass == NULL || str == NULL) { + return ARES_FALSE; + } + + for (i = 0; list[i].name != NULL; i++) { + if (strcasecmp(list[i].name, str) == 0) { + *qclass = list[i].qclass; + return ARES_TRUE; + } + } + return ARES_FALSE; +} + +ares_bool_t ares_dns_rec_type_fromstr(ares_dns_rec_type_t *qtype, + const char *str) +{ + size_t i; + + static const struct { + const char *name; + ares_dns_rec_type_t type; + } list[] = { + {"A", ARES_REC_TYPE_A }, + { "NS", ARES_REC_TYPE_NS }, + { "CNAME", ARES_REC_TYPE_CNAME }, + { "SOA", ARES_REC_TYPE_SOA }, + { "PTR", ARES_REC_TYPE_PTR }, + { "HINFO", ARES_REC_TYPE_HINFO }, + { "MX", ARES_REC_TYPE_MX }, + { "TXT", ARES_REC_TYPE_TXT }, + { "AAAA", ARES_REC_TYPE_AAAA }, + { "SRV", ARES_REC_TYPE_SRV }, + { "NAPTR", ARES_REC_TYPE_NAPTR }, + { "OPT", ARES_REC_TYPE_OPT }, + { "TLSA", ARES_REC_TYPE_TLSA }, + { "SVCB", ARES_REC_TYPE_SVCB }, + { "HTTPS", ARES_REC_TYPE_HTTPS }, + { "ANY", ARES_REC_TYPE_ANY }, + { "URI", ARES_REC_TYPE_URI }, + { "CAA", ARES_REC_TYPE_CAA }, + { "RAW_RR", ARES_REC_TYPE_RAW_RR}, + { NULL, 0 } + }; + + if (qtype == NULL || str == NULL) { + return ARES_FALSE; + } + + for (i = 0; list[i].name != NULL; i++) { + if (strcasecmp(list[i].name, str) == 0) { + *qtype = list[i].type; + return ARES_TRUE; + } + } + return ARES_FALSE; +} + +const char *ares_dns_section_tostr(ares_dns_section_t section) +{ + switch (section) { + case ARES_SECTION_ANSWER: + return "ANSWER"; + case ARES_SECTION_AUTHORITY: + return "AUTHORITY"; + case ARES_SECTION_ADDITIONAL: + return "ADDITIONAL"; + } + return "UNKNOWN"; +} + +static ares_dns_opt_datatype_t ares_dns_opt_get_type_opt(unsigned short opt) +{ + ares_opt_param_t param = (ares_opt_param_t)opt; + switch (param) { + case ARES_OPT_PARAM_LLQ: + /* Really it is u16 version, u16 opcode, u16 error, u64 id, u32 lease */ + return ARES_OPT_DATATYPE_BIN; + case ARES_OPT_PARAM_UL: + return ARES_OPT_DATATYPE_U32; + case ARES_OPT_PARAM_NSID: + return ARES_OPT_DATATYPE_BIN; + case ARES_OPT_PARAM_DAU: + return ARES_OPT_DATATYPE_U8_LIST; + case ARES_OPT_PARAM_DHU: + return ARES_OPT_DATATYPE_U8_LIST; + case ARES_OPT_PARAM_N3U: + return ARES_OPT_DATATYPE_U8_LIST; + case ARES_OPT_PARAM_EDNS_CLIENT_SUBNET: + /* Really it is a u16 address family, u8 source prefix length, + * u8 scope prefix length, address */ + return ARES_OPT_DATATYPE_BIN; + case ARES_OPT_PARAM_EDNS_EXPIRE: + return ARES_OPT_DATATYPE_U32; + case ARES_OPT_PARAM_COOKIE: + /* 8 bytes for client, 16-40 bytes for server */ + return ARES_OPT_DATATYPE_BIN; + case ARES_OPT_PARAM_EDNS_TCP_KEEPALIVE: + /* Timeout in 100ms intervals */ + return ARES_OPT_DATATYPE_U16; + case ARES_OPT_PARAM_PADDING: + /* Arbitrary padding */ + return ARES_OPT_DATATYPE_BIN; + case ARES_OPT_PARAM_CHAIN: + return ARES_OPT_DATATYPE_NAME; + case ARES_OPT_PARAM_EDNS_KEY_TAG: + return ARES_OPT_DATATYPE_U16_LIST; + case ARES_OPT_PARAM_EXTENDED_DNS_ERROR: + /* Really 16bit code followed by textual message */ + return ARES_OPT_DATATYPE_BIN; + } + return ARES_OPT_DATATYPE_BIN; +} + +static ares_dns_opt_datatype_t ares_dns_opt_get_type_svcb(unsigned short opt) +{ + ares_svcb_param_t param = (ares_svcb_param_t)opt; + switch (param) { + case ARES_SVCB_PARAM_NO_DEFAULT_ALPN: + return ARES_OPT_DATATYPE_NONE; + case ARES_SVCB_PARAM_ECH: + return ARES_OPT_DATATYPE_BIN; + case ARES_SVCB_PARAM_MANDATORY: + return ARES_OPT_DATATYPE_U16_LIST; + case ARES_SVCB_PARAM_ALPN: + return ARES_OPT_DATATYPE_STR_LIST; + case ARES_SVCB_PARAM_PORT: + return ARES_OPT_DATATYPE_U16; + case ARES_SVCB_PARAM_IPV4HINT: + return ARES_OPT_DATATYPE_INADDR4_LIST; + case ARES_SVCB_PARAM_IPV6HINT: + return ARES_OPT_DATATYPE_INADDR6_LIST; + } + return ARES_OPT_DATATYPE_BIN; +} + +ares_dns_opt_datatype_t ares_dns_opt_get_datatype(ares_dns_rr_key_t key, + unsigned short opt) +{ + switch (key) { + case ARES_RR_OPT_OPTIONS: + return ares_dns_opt_get_type_opt(opt); + case ARES_RR_SVCB_PARAMS: + case ARES_RR_HTTPS_PARAMS: + return ares_dns_opt_get_type_svcb(opt); + default: + break; + } + return ARES_OPT_DATATYPE_BIN; +} + +static const char *ares_dns_opt_get_name_opt(unsigned short opt) +{ + ares_opt_param_t param = (ares_opt_param_t)opt; + switch (param) { + case ARES_OPT_PARAM_LLQ: + return "LLQ"; + case ARES_OPT_PARAM_UL: + return "UL"; + case ARES_OPT_PARAM_NSID: + return "NSID"; + case ARES_OPT_PARAM_DAU: + return "DAU"; + case ARES_OPT_PARAM_DHU: + return "DHU"; + case ARES_OPT_PARAM_N3U: + return "N3U"; + case ARES_OPT_PARAM_EDNS_CLIENT_SUBNET: + return "edns-client-subnet"; + case ARES_OPT_PARAM_EDNS_EXPIRE: + return "edns-expire"; + case ARES_OPT_PARAM_COOKIE: + return "COOKIE"; + case ARES_OPT_PARAM_EDNS_TCP_KEEPALIVE: + return "edns-tcp-keepalive"; + case ARES_OPT_PARAM_PADDING: + return "Padding"; + case ARES_OPT_PARAM_CHAIN: + return "CHAIN"; + case ARES_OPT_PARAM_EDNS_KEY_TAG: + return "edns-key-tag"; + case ARES_OPT_PARAM_EXTENDED_DNS_ERROR: + return "extended-dns-error"; + } + return NULL; +} + +static const char *ares_dns_opt_get_name_svcb(unsigned short opt) +{ + ares_svcb_param_t param = (ares_svcb_param_t)opt; + switch (param) { + case ARES_SVCB_PARAM_NO_DEFAULT_ALPN: + return "no-default-alpn"; + case ARES_SVCB_PARAM_ECH: + return "ech"; + case ARES_SVCB_PARAM_MANDATORY: + return "mandatory"; + case ARES_SVCB_PARAM_ALPN: + return "alpn"; + case ARES_SVCB_PARAM_PORT: + return "port"; + case ARES_SVCB_PARAM_IPV4HINT: + return "ipv4hint"; + case ARES_SVCB_PARAM_IPV6HINT: + return "ipv6hint"; + } + return NULL; +} + +const char *ares_dns_opt_get_name(ares_dns_rr_key_t key, unsigned short opt) +{ + switch (key) { + case ARES_RR_OPT_OPTIONS: + return ares_dns_opt_get_name_opt(opt); + case ARES_RR_SVCB_PARAMS: + case ARES_RR_HTTPS_PARAMS: + return ares_dns_opt_get_name_svcb(opt); + default: + break; + } + return NULL; +} + +const char *ares_dns_rcode_tostr(ares_dns_rcode_t rcode) +{ + switch (rcode) { + case ARES_RCODE_NOERROR: + return "NOERROR"; + case ARES_RCODE_FORMERR: + return "FORMERR"; + case ARES_RCODE_SERVFAIL: + return "SERVFAIL"; + case ARES_RCODE_NXDOMAIN: + return "NXDOMAIN"; + case ARES_RCODE_NOTIMP: + return "NOTIMP"; + case ARES_RCODE_REFUSED: + return "REFUSED"; + case ARES_RCODE_YXDOMAIN: + return "YXDOMAIN"; + case ARES_RCODE_YXRRSET: + return "YXRRSET"; + case ARES_RCODE_NXRRSET: + return "NXRRSET"; + case ARES_RCODE_NOTAUTH: + return "NOTAUTH"; + case ARES_RCODE_NOTZONE: + return "NOTZONE"; + case ARES_RCODE_DSOTYPEI: + return "DSOTYPEI"; + case ARES_RCODE_BADSIG: + return "BADSIG"; + case ARES_RCODE_BADKEY: + return "BADKEY"; + case ARES_RCODE_BADTIME: + return "BADTIME"; + case ARES_RCODE_BADMODE: + return "BADMODE"; + case ARES_RCODE_BADNAME: + return "BADNAME"; + case ARES_RCODE_BADALG: + return "BADALG"; + case ARES_RCODE_BADTRUNC: + return "BADTRUNC"; + case ARES_RCODE_BADCOOKIE: + return "BADCOOKIE"; + } + + return "UNKNOWN"; +} diff --git a/src/lib/ares_dns_record.c b/src/lib/ares_dns_record.c index af726774..e847e1f9 100644 --- a/src/lib/ares_dns_record.c +++ b/src/lib/ares_dns_record.c @@ -834,7 +834,8 @@ const char *ares_dns_rr_get_str(const ares_dns_rr_t *dns_rr, { char * const *str; - if (ares_dns_rr_key_datatype(key) != ARES_DATATYPE_STR) { + if (ares_dns_rr_key_datatype(key) != ARES_DATATYPE_STR && + ares_dns_rr_key_datatype(key) != ARES_DATATYPE_NAME) { return NULL; } @@ -1090,7 +1091,8 @@ ares_status_t ares_dns_rr_set_str_own(ares_dns_rr_t *dns_rr, { char **str; - if (ares_dns_rr_key_datatype(key) != ARES_DATATYPE_STR) { + if (ares_dns_rr_key_datatype(key) != ARES_DATATYPE_STR && + ares_dns_rr_key_datatype(key) != ARES_DATATYPE_NAME) { return ARES_EFORMERR; } @@ -1190,6 +1192,7 @@ ares_status_t ares_dns_rr_set_opt_own(ares_dns_rr_t *dns_rr, done: ares_free((*options)->optval[idx].val); + (*options)->optval[idx].opt = opt; (*options)->optval[idx].val = val; (*options)->optval[idx].val_len = val_len; @@ -1219,3 +1222,68 @@ ares_status_t ares_dns_rr_set_opt(ares_dns_rr_t *dns_rr, ares_dns_rr_key_t key, return status; } + +char *ares_dns_addr_to_ptr(const struct ares_addr *addr) +{ + ares__buf_t *buf = NULL; + const unsigned char *ptr = NULL; + size_t ptr_len = 0; + size_t i; + ares_status_t status; + static const unsigned char hexbytes[] = "0123456789abcdef"; + + if (addr->family != AF_INET && addr->family != AF_INET6) + goto fail; + + buf = ares__buf_create(); + if (buf == NULL) + goto fail; + + if (addr->family == AF_INET) { + ptr = (const unsigned char *)&addr->addr.addr4; + ptr_len = 4; + } else { + ptr = (const unsigned char *)&addr->addr.addr6; + ptr_len = 16; + } + + for (i=ptr_len; i>0; i--) { + if (addr->family == AF_INET) { + status = ares__buf_append_num_dec(buf, (size_t)ptr[i-1], 0); + } else { + unsigned char c; + + c = ptr[i-1] & 0xF; + status = ares__buf_append_byte(buf, hexbytes[c]); + if (status != ARES_SUCCESS) + goto fail; + + status = ares__buf_append_byte(buf, '.'); + if (status != ARES_SUCCESS) + goto fail; + + c = (ptr[i-1] >> 4) & 0xF; + status = ares__buf_append_byte(buf, hexbytes[c]); + } + if (status != ARES_SUCCESS) + goto fail; + + status = ares__buf_append_byte(buf, '.'); + if (status != ARES_SUCCESS) + goto fail; + } + + if (addr->family == AF_INET) { + status = ares__buf_append(buf, (const unsigned char *)"in-addr.arpa", 12); + } else { + status = ares__buf_append(buf, (const unsigned char *)"ip6.arpa", 8); + } + if (status != ARES_SUCCESS) + goto fail; + + return ares__buf_finish_str(buf, NULL); + +fail: + ares__buf_destroy(buf); + return NULL; +} diff --git a/src/lib/ares_gethostbyaddr.c b/src/lib/ares_gethostbyaddr.c index 5df2aeff..c678d660 100644 --- a/src/lib/ares_gethostbyaddr.c +++ b/src/lib/ares_gethostbyaddr.c @@ -67,8 +67,6 @@ static void end_aquery(struct addr_query *aquery, ares_status_t status, static ares_status_t file_lookup(ares_channel_t *channel, const struct ares_addr *addr, struct hostent **host); -static void ptr_rr_name(char *name, size_t name_size, - const struct ares_addr *addr); void ares_gethostbyaddr(ares_channel_t *channel, const void *addr, int addrlen, int family, ares_host_callback callback, void *arg) @@ -106,19 +104,25 @@ void ares_gethostbyaddr(ares_channel_t *channel, const void *addr, int addrlen, next_lookup(aquery); } + static void next_lookup(struct addr_query *aquery) { const char *p; - char name[128]; ares_status_t status; struct hostent *host; + char *name; for (p = aquery->remaining_lookups; *p; p++) { switch (*p) { case 'b': - ptr_rr_name(name, sizeof(name), &aquery->addr); + name = ares_dns_addr_to_ptr(&aquery->addr); + if (name == NULL) { + end_aquery(aquery, ARES_ENOMEM, NULL); + return; + } aquery->remaining_lookups = p + 1; ares_query(aquery->channel, name, C_IN, T_PTR, addr_callback, aquery); + ares_free(name); return; case 'f': status = file_lookup(aquery->channel, &aquery->addr, &host); @@ -210,31 +214,3 @@ static ares_status_t file_lookup(ares_channel_t *channel, return ARES_SUCCESS; } -static void ptr_rr_name(char *name, size_t name_size, - const struct ares_addr *addr) -{ - if (addr->family == AF_INET) { - unsigned long laddr = ntohl(addr->addr.addr4.s_addr); - unsigned long a1 = (laddr >> 24UL) & 0xFFUL; - unsigned long a2 = (laddr >> 16UL) & 0xFFUL; - unsigned long a3 = (laddr >> 8UL) & 0xFFUL; - unsigned long a4 = laddr & 0xFFUL; - snprintf(name, name_size, "%lu.%lu.%lu.%lu.in-addr.arpa", a4, a3, a2, a1); - } else { - const unsigned char *bytes = (const unsigned char *)&addr->addr.addr6; - /* There are too many arguments to do this in one line using - * minimally C89-compliant compilers */ - snprintf(name, name_size, - "%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.", - bytes[15] & 0xf, bytes[15] >> 4, bytes[14] & 0xf, bytes[14] >> 4, - bytes[13] & 0xf, bytes[13] >> 4, bytes[12] & 0xf, bytes[12] >> 4, - bytes[11] & 0xf, bytes[11] >> 4, bytes[10] & 0xf, bytes[10] >> 4, - bytes[9] & 0xf, bytes[9] >> 4, bytes[8] & 0xf, bytes[8] >> 4); - snprintf(name + ares_strlen(name), name_size - ares_strlen(name), - "%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.ip6.arpa", - bytes[7] & 0xf, bytes[7] >> 4, bytes[6] & 0xf, bytes[6] >> 4, - bytes[5] & 0xf, bytes[5] >> 4, bytes[4] & 0xf, bytes[4] >> 4, - bytes[3] & 0xf, bytes[3] >> 4, bytes[2] & 0xf, bytes[2] >> 4, - bytes[1] & 0xf, bytes[1] >> 4, bytes[0] & 0xf, bytes[0] >> 4); - } -} diff --git a/src/lib/ares_math.c b/src/lib/ares_math.c index 3c067e5d..41225012 100644 --- a/src/lib/ares_math.c +++ b/src/lib/ares_math.c @@ -65,3 +65,48 @@ size_t ares__log2(size_t n) return tab64[(n * 0x07EDD5E59A4E28C2) >> 58]; } + +/* x^y */ +size_t ares__pow(size_t x, size_t y) +{ + size_t res = 1; + + while (y > 0) { + /* If y is odd, multiply x with result */ + if (y & 1) { + res = res * x; + } + + /* y must be even now */ + y = y >> 1; /* y /= 2; */ + x = x * x; /* x^2 */ + } + + return res; +} + +size_t ares__count_digits(size_t n) +{ + size_t digits; + + for (digits = 0; n > 0; digits++) { + n /= 10; + } + if (digits == 0) + digits = 1; + + return digits; +} + +size_t ares__count_hexdigits(size_t n) +{ + size_t digits; + + for (digits = 0; n > 0; digits++) { + n /= 16; + } + if (digits == 0) + digits = 1; + + return digits; +} diff --git a/src/lib/ares_private.h b/src/lib/ares_private.h index 0bbb68e5..9121e070 100644 --- a/src/lib/ares_private.h +++ b/src/lib/ares_private.h @@ -151,14 +151,6 @@ typedef struct ares_rand_state ares_rand_state; /********* EDNS defines section ******/ -struct ares_addr { - int family; - - union { - struct in_addr addr4; - struct ares_in6_addr addr6; - } addr; -}; struct query; @@ -353,7 +345,8 @@ ares_status_t ares_query_qid(ares_channel_t *channel, const char *name, /* Identical to ares_send() except returns normal ares return codes like * ARES_SUCCESS */ ares_status_t ares_send_ex(ares_channel_t *channel, const unsigned char *qbuf, - size_t qlen, ares_callback callback, void *arg); + size_t qlen, ares_callback callback, void *arg, + unsigned short *qid); void ares__close_connection(struct server_connection *conn); void ares__close_sockets(struct server_state *server); void ares__check_cleanup_conn(const ares_channel_t *channel, @@ -558,5 +551,9 @@ ares_status_t ares__dns_name_write(ares__buf_t *buf, ares__llist_t **list, size_t ares__round_up_pow2(size_t n); size_t ares__log2(size_t n); +size_t ares__pow(size_t x, size_t y); +size_t ares__count_digits(size_t n); +size_t ares__count_hexdigits(size_t n); + #endif /* __ARES_PRIVATE_H */ diff --git a/src/lib/ares_process.c b/src/lib/ares_process.c index 3f350450..a9e12a4e 100644 --- a/src/lib/ares_process.c +++ b/src/lib/ares_process.c @@ -656,7 +656,7 @@ static ares_status_t process_answer(ares_channel_t *channel, /* If we use EDNS and server answers with FORMERR without an OPT RR, the * protocol extension is not understood by the responder. We must retry the * query without EDNS enabled. */ - if (ares_dns_record_get_rcode(rdnsrec) == ARES_RCODE_FORMAT_ERROR && + if (ares_dns_record_get_rcode(rdnsrec) == ARES_RCODE_FORMERR && has_opt_rr(qdnsrec) && !has_opt_rr(rdnsrec)) { status = rewrite_without_edns(qdnsrec, query); if (status != ARES_SUCCESS) { @@ -686,13 +686,13 @@ static ares_status_t process_answer(ares_channel_t *channel, */ if (!(channel->flags & ARES_FLAG_NOCHECKRESP)) { ares_dns_rcode_t rcode = ares_dns_record_get_rcode(rdnsrec); - if (rcode == ARES_RCODE_SERVER_FAILURE || - rcode == ARES_RCODE_NOT_IMPLEMENTED || rcode == ARES_RCODE_REFUSED) { + if (rcode == ARES_RCODE_SERVFAIL || + rcode == ARES_RCODE_NOTIMP || rcode == ARES_RCODE_REFUSED) { switch (rcode) { - case ARES_RCODE_SERVER_FAILURE: + case ARES_RCODE_SERVFAIL: query->error_status = ARES_ESERVFAIL; break; - case ARES_RCODE_NOT_IMPLEMENTED: + case ARES_RCODE_NOTIMP: query->error_status = ARES_ENOTIMP; break; case ARES_RCODE_REFUSED: diff --git a/src/lib/ares_query.c b/src/lib/ares_query.c index 88f3c7b8..bb64d9a9 100644 --- a/src/lib/ares_query.c +++ b/src/lib/ares_query.c @@ -45,21 +45,6 @@ struct qquery { static void qcallback(void *arg, int status, int timeouts, unsigned char *abuf, int alen); -/* a unique query id is generated using an rc4 key. Since the id may already - be used by a running query (as infrequent as it may be), a lookup is - performed per id generation. In practice this search should happen only - once per newly generated id -*/ -static unsigned short generate_unique_id(ares_channel_t *channel) -{ - unsigned short id; - - do { - id = ares__generate_new_id(channel->rand_state); - } while (ares__htable_szvp_get(channel->queries_by_qid, id, NULL)); - - return id; -} ares_status_t ares_query_qid(ares_channel_t *channel, const char *name, int dnsclass, int type, ares_callback callback, @@ -70,12 +55,11 @@ ares_status_t ares_query_qid(ares_channel_t *channel, const char *name, int qlen; int rd; ares_status_t status; - unsigned short id = generate_unique_id(channel); /* Compose the query. */ rd = !(channel->flags & ARES_FLAG_NORECURSE); status = (ares_status_t)ares_create_query( - name, dnsclass, type, id, rd, &qbuf, &qlen, + name, dnsclass, type, 0, rd, &qbuf, &qlen, (channel->flags & ARES_FLAG_EDNS) ? (int)channel->ednspsz : 0); if (status != ARES_SUCCESS) { if (qbuf != NULL) { @@ -96,13 +80,9 @@ ares_status_t ares_query_qid(ares_channel_t *channel, const char *name, qquery->arg = arg; /* Send it off. qcallback will be called when we get an answer. */ - status = ares_send_ex(channel, qbuf, (size_t)qlen, qcallback, qquery); + status = ares_send_ex(channel, qbuf, (size_t)qlen, qcallback, qquery, qid); ares_free_string(qbuf); - if (status == ARES_SUCCESS && qid) { - *qid = id; - } - return status; } diff --git a/src/lib/ares_send.c b/src/lib/ares_send.c index aff65eac..eefdfe46 100644 --- a/src/lib/ares_send.c +++ b/src/lib/ares_send.c @@ -37,12 +37,27 @@ #include "ares_dns.h" #include "ares_private.h" + +static unsigned short generate_unique_qid(ares_channel_t *channel) +{ + unsigned short id; + + do { + id = ares__generate_new_id(channel->rand_state); + } while (ares__htable_szvp_get(channel->queries_by_qid, id, NULL)); + + return id; +} + ares_status_t ares_send_ex(ares_channel_t *channel, const unsigned char *qbuf, - size_t qlen, ares_callback callback, void *arg) + size_t qlen, ares_callback callback, void *arg, + unsigned short *qid) { struct query *query; size_t packetsz; struct timeval now; + ares_status_t status; + unsigned short id = generate_unique_qid(channel); /* Verify that the query is at least long enough to hold the header. */ if (qlen < HFIXEDSZ || qlen >= (1 << 16)) { @@ -69,12 +84,14 @@ ares_status_t ares_send_ex(ares_channel_t *channel, const unsigned char *qbuf, return ARES_ENOMEM; } - /* Compute the query ID. Start with no timeout. */ - query->qid = DNS_HEADER_QID(qbuf); + query->qid = id; query->timeout.tv_sec = 0; query->timeout.tv_usec = 0; - memcpy(query->qbuf, qbuf, qlen); + /* Ignore first 2 bytes, assign our own query id */ + query->qbuf[0] = (unsigned char)((id >> 8) & 0xFF); + query->qbuf[1] = (unsigned char)(id & 0xFF); + memcpy(query->qbuf + 2, qbuf + 2, qlen - 2); query->qlen = qlen; /* Fill in query arguments. */ @@ -115,11 +132,15 @@ ares_status_t ares_send_ex(ares_channel_t *channel, const unsigned char *qbuf, /* Perform the first query action. */ now = ares__tvnow(); - return ares__send_query(query, &now); + status = ares__send_query(query, &now); + if (status == ARES_SUCCESS && qid) { + *qid = id; + } + return status; } void ares_send(ares_channel_t *channel, const unsigned char *qbuf, int qlen, ares_callback callback, void *arg) { - ares_send_ex(channel, qbuf, (size_t)qlen, callback, arg); + ares_send_ex(channel, qbuf, (size_t)qlen, callback, arg, NULL); } diff --git a/src/tools/adig.c b/src/tools/adig.c index 2c64c986..e7be1b1c 100644 --- a/src/tools/adig.c +++ b/src/tools/adig.c @@ -24,7 +24,6 @@ * * SPDX-License-Identifier: MIT */ - #include "ares_setup.h" #ifdef HAVE_NETINET_IN_H @@ -45,7 +44,6 @@ #include "ares.h" #include "ares_dns.h" -#include "ares_getopt.h" #ifndef HAVE_STRDUP # include "ares_strdup.h" @@ -62,17 +60,30 @@ # define strncasecmp(p1, p2, n) ares_strncasecmp(p1, p2, n) #endif +#include "ares_getopt.h" + #ifdef WATT32 # undef WIN32 /* Redefined in MingW headers */ #endif -struct nv { +typedef struct { + ares_bool_t is_help; + struct ares_options options; + int optmask; + ares_dns_class_t qclass; + ares_dns_rec_type_t qtype; + int args_processed; + char *servers; + char error[256]; +} adig_config_t; + +typedef struct { const char *name; int value; -}; +} nv_t; -static const struct nv flags[] = { +static const nv_t configflags[] = { {"usevc", ARES_FLAG_USEVC }, { "primary", ARES_FLAG_PRIMARY }, { "igntc", ARES_FLAG_IGNTC }, @@ -80,995 +91,843 @@ static const struct nv flags[] = { { "stayopen", ARES_FLAG_STAYOPEN }, { "noaliases", ARES_FLAG_NOALIASES} }; -static const int nflags = sizeof(flags) / sizeof(flags[0]); - -static const struct nv classes[] = { - {"IN", C_IN }, - { "CHAOS", C_CHAOS}, - { "HS", C_HS }, - { "ANY", C_ANY } -}; -static const int nclasses = sizeof(classes) / sizeof(classes[0]); - -static const struct nv types[] = { - {"A", T_A }, - { "NS", T_NS }, - { "MD", T_MD }, - { "MF", T_MF }, - { "CNAME", T_CNAME }, - { "SOA", T_SOA }, - { "MB", T_MB }, - { "MG", T_MG }, - { "MR", T_MR }, - { "NULL", T_NULL }, - { "WKS", T_WKS }, - { "PTR", T_PTR }, - { "HINFO", T_HINFO }, - { "MINFO", T_MINFO }, - { "MX", T_MX }, - { "TXT", T_TXT }, - { "RP", T_RP }, - { "AFSDB", T_AFSDB }, - { "X25", T_X25 }, - { "ISDN", T_ISDN }, - { "RT", T_RT }, - { "NSAP", T_NSAP }, - { "NSAP_PTR", T_NSAP_PTR}, - { "SIG", T_SIG }, - { "KEY", T_KEY }, - { "PX", T_PX }, - { "GPOS", T_GPOS }, - { "AAAA", T_AAAA }, - { "LOC", T_LOC }, - { "SRV", T_SRV }, - { "AXFR", T_AXFR }, - { "MAILB", T_MAILB }, - { "MAILA", T_MAILA }, - { "NAPTR", T_NAPTR }, - { "DS", T_DS }, - { "SSHFP", T_SSHFP }, - { "RRSIG", T_RRSIG }, - { "NSEC", T_NSEC }, - { "DNSKEY", T_DNSKEY }, - { "CAA", T_CAA }, - { "URI", T_URI }, - { "ANY", T_ANY } -}; -static const int ntypes = sizeof(types) / sizeof(types[0]); - -static const char *opcodes[] = { - "QUERY", "IQUERY", "STATUS", "(reserved)", "NOTIFY", "(unknown)", - "(unknown)", "(unknown)", "(unknown)", "UPDATEA", "UPDATED", "UPDATEDA", - "UPDATEM", "UPDATEMA", "ZONEINIT", "ZONEREF" -}; +static const size_t nconfigflags = sizeof(configflags) / sizeof(*configflags); -static const char *rcodes[] = { - "NOERROR", "FORMERR", "SERVFAIL", "NXDOMAIN", "NOTIMP", "REFUSED", - "(unknown)", "(unknown)", "(unknown)", "(unknown)", "(unknown)", "(unknown)", - "(unknown)", "(unknown)", "(unknown)", "NOCHANGE" -}; - -static void callback(void *arg, int status, int timeouts, unsigned char *abuf, - int alen); -static const unsigned char *display_question(const unsigned char *aptr, - const unsigned char *abuf, - int alen); -static const unsigned char *display_rr(const unsigned char *aptr, - const unsigned char *abuf, int alen); -static int convert_query(char **name, int use_bitstring); -static const char *type_name(int type); -static const char *class_name(int dnsclass); -static void usage(void); -static void destroy_addr_list(struct ares_addr_node *head); -static void append_addr_list(struct ares_addr_node **head, - struct ares_addr_node *node); -static void print_help_info_adig(void); - -static size_t ares_strcpy(char *dest, const char *src, size_t dest_size) +static int lookup_flag(const nv_t *nv, size_t num_nv, const char *name) { - size_t len = 0; + size_t i; - if (dest == NULL || dest_size == 0) { + if (name == NULL) { return 0; } - if (src != NULL) { - len = strlen(src); + for (i = 0; i < num_nv; i++) { + if (strcasecmp(nv[i].name, name) == 0) { + return nv[i].value; + } } - if (len >= dest_size) { - len = dest_size - 1; - } + return 0; +} - if (len) { - memcpy(dest, src, len); - } +static void free_config(adig_config_t *config) +{ + free(config->servers); + memset(config, 0, sizeof(*config)); +} - dest[len] = 0; - return len; +static void print_help(void) +{ + printf("adig version %s\n\n", ares_version(NULL)); + printf( + "usage: adig [-h] [-d] [-f flag] [[-s server] ...] [-T|U port] [-c class]\n" + " [-t type] name ...\n\n" + " -h : Display this help and exit.\n" + " -d : Print some extra debugging output.\n" + " -f flag : Add a behavior control flag. Possible values are\n" + " igntc - do not retry a truncated query as TCP, just\n" + " return the truncated answer\n" + " noaliases - don't honor the HOSTALIASES environment\n" + " variable\n" + " norecurse - don't query upstream servers recursively\n" + " primary - use the first server\n" + " stayopen - don't close the communication sockets\n" + " usevc - use TCP only\n" + " -s server : Connect to the specified DNS server, instead of the\n" + " system's default one(s). Servers are tried in round-robin,\n" + " if the previous one failed.\n" + " -T port : Connect to the specified TCP port of DNS server.\n" + " -U port : Connect to the specified UDP port of DNS server.\n" + " -c class : Set the query class. Possible values for class are:\n" + " ANY, CHAOS, HS and IN (default)\n" + " -t type : Query records of the specified type. Possible values for\n" + " type are:\n" + " A (default), AAAA, ANY, CNAME, HINFO, MX, NAPTR, NS, PTR,\n" + " SOA, SRV, TXT, TLSA, URI, CAA, SVCB, HTTPS\n\n"); } -int main(int argc, char **argv) +static ares_bool_t read_cmdline(int argc, const char **argv, adig_config_t *config) { - ares_channel_t *channel; - int c; - int i; - int optmask = ARES_OPT_FLAGS; - int dnsclass = C_IN; - int type = T_A; - int status; - int nfds; - int count; - int use_ptr_helper = 0; - struct ares_options options; - struct hostent *hostent; - fd_set read_fds; - fd_set write_fds; - struct timeval *tvp; - struct timeval tv; - struct ares_addr_node *srvr; - struct ares_addr_node *servers = NULL; + ares_getopt_state_t state; + ares_getopt_init(&state, argc, argv); -#ifdef USE_WINSOCK - WORD wVersionRequested = MAKEWORD(USE_WINSOCK, USE_WINSOCK); - WSADATA wsaData; - WSAStartup(wVersionRequested, &wsaData); -#endif + while (1) { + int c; + int f; - status = ares_library_init(ARES_LIB_INIT_ALL); - if (status != ARES_SUCCESS) { - fprintf(stderr, "ares_library_init: %s\n", ares_strerror(status)); - return 1; - } + c = ares_getopt(&state, "dh?f:s:c:t:T:U:"); + if (c == -1) + break; - options.flags = ARES_FLAG_NOCHECKRESP; - options.servers = NULL; - options.nservers = 0; - while ((c = ares_getopt(argc, argv, "dh?f:s:c:t:T:U:x")) != -1) { switch (c) { case 'd': #ifdef WATT32 dbug_init(); #endif break; + case 'h': - print_help_info_adig(); - break; case '?': - print_help_info_adig(); - break; + config->is_help = ARES_TRUE; + return ARES_TRUE; + case 'f': - /* Add a flag. */ - for (i = 0; i < nflags; i++) { - if (strcmp(flags[i].name, optarg) == 0) { - break; - } - } - if (i < nflags) { - options.flags |= flags[i].value; - } else { - usage(); + f = lookup_flag(configflags, nconfigflags, state.optarg); + if (f == 0) { + snprintf(config->error, sizeof(config->error), "flag %s unknown", + state.optarg); } + + config->options.flags |= f; + config->optmask |= ARES_OPT_FLAGS; break; case 's': - /* User-specified name servers override default ones. */ - srvr = malloc(sizeof(struct ares_addr_node)); - if (!srvr) { - fprintf(stderr, "Out of memory!\n"); - destroy_addr_list(servers); - return 1; + if (state.optarg == NULL) { + snprintf(config->error, sizeof(config->error), "%s", + "missing servers"); + return ARES_FALSE; } - append_addr_list(&servers, srvr); - if (ares_inet_pton(AF_INET, optarg, &srvr->addr.addr4) > 0) { - srvr->family = AF_INET; - } else if (ares_inet_pton(AF_INET6, optarg, &srvr->addr.addr6) > 0) { - srvr->family = AF_INET6; - } else { - hostent = gethostbyname(optarg); - if (!hostent) { - fprintf(stderr, "adig: server %s not found.\n", optarg); - destroy_addr_list(servers); - return 1; - } - switch (hostent->h_addrtype) { - case AF_INET: - srvr->family = AF_INET; - memcpy(&srvr->addr.addr4, hostent->h_addr, - sizeof(srvr->addr.addr4)); - break; - case AF_INET6: - srvr->family = AF_INET6; - memcpy(&srvr->addr.addr6, hostent->h_addr, - sizeof(srvr->addr.addr6)); - break; - default: - fprintf(stderr, "adig: server %s unsupported address family.\n", - optarg); - destroy_addr_list(servers); - return 1; - } + if (config->servers) { + free(config->servers); } - /* Notice that calling ares_init_options() without servers in the - * options struct and with ARES_OPT_SERVERS set simultaneously in - * the options mask, results in an initialization with no servers. - * When alternative name servers have been specified these are set - * later calling ares_set_servers() overriding any existing server - * configuration. To prevent initial configuration with default - * servers that will be discarded later, ARES_OPT_SERVERS is set. - * If this flag is not set here the result shall be the same but - * ares_init_options() will do needless work. */ - optmask |= ARES_OPT_SERVERS; + config->servers = strdup(state.optarg); break; case 'c': - /* Set the query class. */ - for (i = 0; i < nclasses; i++) { - if (strcasecmp(classes[i].name, optarg) == 0) { - break; - } - } - if (i < nclasses) { - dnsclass = classes[i].value; - } else { - usage(); + if (!ares_dns_class_fromstr(&config->qclass, state.optarg)) { + snprintf(config->error, sizeof(config->error), "unrecognied class %s", + state.optarg); + return ARES_FALSE; } break; case 't': - /* Set the query type. */ - for (i = 0; i < ntypes; i++) { - if (strcasecmp(types[i].name, optarg) == 0) { - break; - } - } - if (i < ntypes) { - type = types[i].value; - } else { - usage(); + if (!ares_dns_rec_type_fromstr(&config->qtype, state.optarg)) { + snprintf(config->error, sizeof(config->error), "unrecognied type %s", + state.optarg); + return ARES_FALSE; } break; case 'T': /* Set the TCP port number. */ - if (!ISDIGIT(*optarg)) { - usage(); + if (!isdigit(*state.optarg)) { + snprintf(config->error, sizeof(config->error), "invalid port number"); + return ARES_FALSE; } - options.tcp_port = (unsigned short)strtol(optarg, NULL, 0); - options.flags |= ARES_FLAG_USEVC; - optmask |= ARES_OPT_TCP_PORT; + config->options.tcp_port = (unsigned short)strtol(state.optarg, NULL, 0); + config->options.flags |= ARES_FLAG_USEVC; + config->optmask |= ARES_OPT_TCP_PORT; break; case 'U': /* Set the UDP port number. */ - if (!ISDIGIT(*optarg)) { - usage(); + if (!isdigit(*state.optarg)) { + snprintf(config->error, sizeof(config->error), "invalid port number"); + return ARES_FALSE; } - options.udp_port = (unsigned short)strtol(optarg, NULL, 0); - optmask |= ARES_OPT_UDP_PORT; - break; - - case 'x': - use_ptr_helper++; + config->options.udp_port = (unsigned short)strtol(state.optarg, NULL, 0); + config->optmask |= ARES_OPT_UDP_PORT; break; } } - argc -= optind; - argv += optind; - if (argc == 0) { - usage(); - } - status = ares_init_options(&channel, &options, optmask); + config->args_processed = state.optind; - if (status != ARES_SUCCESS) { - fprintf(stderr, "ares_init_options: %s\n", ares_strerror(status)); - return 1; + if (config->args_processed >= argc) { + snprintf(config->error, sizeof(config->error), "missing query name"); + return ARES_FALSE; } - if (servers) { - status = ares_set_servers(channel, servers); - destroy_addr_list(servers); - if (status != ARES_SUCCESS) { - fprintf(stderr, "ares_init_options: %s\n", ares_strerror(status)); - return 1; - } - } - - /* Initiate the queries, one per command-line argument. If there is - * only one query to do, supply NULL as the callback argument; - * otherwise, supply the query name as an argument so we can - * distinguish responses for the user when printing them out. - */ - for (i = 1; *argv; i++, argv++) { - char *query = *argv; - - if (type == T_PTR && dnsclass == C_IN && use_ptr_helper) { - if (!convert_query(&query, use_ptr_helper >= 2)) { - continue; - } - } + return ARES_TRUE; +} - ares_query(channel, query, dnsclass, type, callback, - i < argc - 1 ? (void *)query : NULL); +static void print_flags(ares_dns_flags_t flags) +{ + if (flags & ARES_FLAG_QR) { + printf(" qr"); } + if (flags & ARES_FLAG_AA) { + printf(" aa"); + } + if (flags & ARES_FLAG_TC) { + printf(" tc"); + } + if (flags & ARES_FLAG_RD) { + printf(" rd"); + } + if (flags & ARES_FLAG_RA) { + printf(" ra"); + } + if (flags & ARES_FLAG_AD) { + printf(" ad"); + } + if (flags & ARES_FLAG_CD) { + printf(" cd"); + } +} - /* Wait for all queries to complete. */ - for (;;) { - FD_ZERO(&read_fds); - FD_ZERO(&write_fds); - nfds = ares_fds(channel, &read_fds, &write_fds); - if (nfds == 0) { - break; - } - tvp = ares_timeout(channel, NULL, &tv); - if (tvp == NULL) { - break; +static void print_header(const ares_dns_record_t *dnsrec) +{ + printf(";; ->>HEADER<<- opcode: %s, status: %s, id: %u\n", + ares_dns_opcode_tostr(ares_dns_record_get_opcode(dnsrec)), + ares_dns_rcode_tostr(ares_dns_record_get_rcode(dnsrec)), + ares_dns_record_get_id(dnsrec)); + printf(";; flags:"); + print_flags(ares_dns_record_get_flags(dnsrec)); + printf("; QUERY: %u, ANSWER: %u, AUTHORITY: %u, ADDITIONAL: %u\n\n", + (unsigned int)ares_dns_record_query_cnt(dnsrec), + (unsigned int)ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_ANSWER), + (unsigned int)ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_AUTHORITY), + (unsigned int)ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_ADDITIONAL)); +} + +static void print_question(const ares_dns_record_t *dnsrec) +{ + size_t i; + printf(";; QUESTION SECTION:\n"); + for (i = 0; i < ares_dns_record_query_cnt(dnsrec); i++) { + const char *name; + ares_dns_rec_type_t qtype; + ares_dns_class_t qclass; + size_t len; + ares_dns_record_query_get(dnsrec, i, &name, &qtype, &qclass); + len = strlen(name); + printf(";%s.\t", name); + if (len + 1 < 24) { + printf("\t"); } - count = select(nfds, &read_fds, &write_fds, NULL, tvp); - if (count < 0 && (status = SOCKERRNO) != EINVAL) { - printf("select fail: %d", status); - return 1; + if (len + 1 < 16) { + printf("\t"); } - ares_process(channel, &read_fds, &write_fds); + printf("%s\t%s\n", ares_dns_class_tostr(qclass), + ares_dns_rec_type_tostr(qtype)); } + printf("\n"); +} - ares_destroy(channel); +static void print_opt_none(const unsigned char *val, size_t val_len) +{ + (void)val; + if (val_len != 0) { + printf("INVALID!"); + } +} - ares_library_cleanup(); +static void print_opt_addr_list(const unsigned char *val, size_t val_len) +{ + size_t i; + if (val_len % 4 != 0) { + printf("INVALID!"); + return; + } + for (i = 0; i < val_len; i += 4) { + char buf[256] = ""; + ares_inet_ntop(AF_INET, val + i, buf, sizeof(buf)); + if (i != 0) { + printf(","); + } + printf("%s", buf); + } +} -#ifdef USE_WINSOCK - WSACleanup(); -#endif +static void print_opt_addr6_list(const unsigned char *val, size_t val_len) +{ + size_t i; + if (val_len % 16 != 0) { + printf("INVALID!"); + return; + } + for (i = 0; i < val_len; i += 16) { + char buf[256] = ""; - return 0; + ares_inet_ntop(AF_INET6, val + i, buf, sizeof(buf)); + if (i != 0) { + printf(","); + } + printf("%s", buf); + } } -static void callback(void *arg, int status, int timeouts, unsigned char *abuf, - int alen) +static void print_opt_u8_list(const unsigned char *val, size_t val_len) { - char *name = (char *)arg; - int id; - int qr; - int opcode; - int aa; - int tc; - int rd; - int ra; - int rcode; - unsigned int qdcount; - unsigned int ancount; - unsigned int nscount; - unsigned int arcount; - unsigned int i; - const unsigned char *aptr; + size_t i; - (void)timeouts; - - /* Display the query name if given. */ - if (name) { - printf("Answer for query %s:\n", name); + for (i = 0; i < val_len; i++) { + if (i != 0) { + printf(","); + } + printf("%u", (unsigned int)val[i]); } +} - /* Display an error message if there was an error, but only stop if - * we actually didn't get an answer buffer. - */ - if (status != ARES_SUCCESS) { - printf("%s\n", ares_strerror(status)); - if (!abuf) { - return; +static void print_opt_u16_list(const unsigned char *val, size_t val_len) +{ + size_t i; + if (val_len < 2 || val_len % 2 != 0) { + printf("INVALID!"); + return; + } + for (i = 0; i < val_len; i += 2) { + unsigned short u16 = 0; + unsigned short c; + /* Jumping over backwards to try to avoid odd compiler warnings */ + c = (unsigned short)val[i]; + u16 |= (unsigned short)((c << 8) & 0xFFFF); + c = (unsigned short)val[i+1]; + u16 |= c; + if (i != 0) { + printf(","); } + printf("%u", (unsigned int)u16); } +} - /* Won't happen, but check anyway, for safety. */ - if (alen < HFIXEDSZ) { +static void print_opt_u32_list(const unsigned char *val, size_t val_len) +{ + size_t i; + if (val_len < 4 || val_len % 4 != 0) { + printf("INVALID!"); return; } + for (i = 0; i < val_len; i += 4) { + unsigned int u32 = 0; - /* Parse the answer header. */ - id = DNS_HEADER_QID(abuf); - qr = DNS_HEADER_QR(abuf); - opcode = DNS_HEADER_OPCODE(abuf); - aa = DNS_HEADER_AA(abuf); - tc = DNS_HEADER_TC(abuf); - rd = DNS_HEADER_RD(abuf); - ra = DNS_HEADER_RA(abuf); - rcode = DNS_HEADER_RCODE(abuf); - qdcount = DNS_HEADER_QDCOUNT(abuf); - ancount = DNS_HEADER_ANCOUNT(abuf); - nscount = DNS_HEADER_NSCOUNT(abuf); - arcount = DNS_HEADER_ARCOUNT(abuf); - - /* Display the answer header. */ - printf("id: %d\n", id); - printf("flags: %s%s%s%s%s\n", qr ? "qr " : "", aa ? "aa " : "", - tc ? "tc " : "", rd ? "rd " : "", ra ? "ra " : ""); - printf("opcode: %s\n", opcodes[opcode]); - printf("rcode: %s\n", rcodes[rcode]); - - /* Display the questions. */ - printf("Questions:\n"); - aptr = abuf + HFIXEDSZ; - for (i = 0; i < qdcount; i++) { - aptr = display_question(aptr, abuf, alen); - if (aptr == NULL) { - return; + u32 |= (unsigned int)(val[i] << 24); + u32 |= (unsigned int)(val[i + 1] << 16); + u32 |= (unsigned int)(val[i + 2] << 8); + u32 |= (unsigned int)(val[i + 3]); + if (i != 0) { + printf(","); } + printf("%u", u32); } +} - /* Display the answers. */ - printf("Answers:\n"); - for (i = 0; i < ancount; i++) { - aptr = display_rr(aptr, abuf, alen); - if (aptr == NULL) { - return; - } - } +static void print_opt_str_list(const unsigned char *val, size_t val_len) +{ + size_t cnt = 0; - /* Display the NS records. */ - printf("NS records:\n"); - for (i = 0; i < nscount; i++) { - aptr = display_rr(aptr, abuf, alen); - if (aptr == NULL) { - return; + printf("\""); + while (val_len) { + long read_len = 0; + unsigned char *str = NULL; + ares_status_t status; + + if (cnt) { + printf(","); } - } - /* Display the additional records. */ - printf("Additional records:\n"); - for (i = 0; i < arcount; i++) { - aptr = display_rr(aptr, abuf, alen); - if (aptr == NULL) { - return; + status = (ares_status_t)ares_expand_string(val, val, (int)val_len, &str, + &read_len); + if (status != ARES_SUCCESS) { + printf("INVALID"); + break; } + printf("%s", str); + ares_free_string(str); + val_len -= (size_t)read_len; + val += read_len; + cnt++; } + printf("\""); } -static const unsigned char *display_question(const unsigned char *aptr, - const unsigned char *abuf, - int alen) +static void print_opt_name(const unsigned char *val, size_t val_len) { - char *name; - int type; - int dnsclass; - int status; - long len; - - /* Parse the question name. */ - status = ares_expand_name(aptr, abuf, alen, &name, &len); - if (status != ARES_SUCCESS) { - return NULL; - } - aptr += len; - - /* Make sure there's enough data after the name for the fixed part - * of the question. - */ - if (aptr + QFIXEDSZ > abuf + alen) { - ares_free_string(name); - return NULL; - } + char *str = NULL; + long read_len = 0; - /* Parse the question type and class. */ - type = DNS_QUESTION_TYPE(aptr); - dnsclass = DNS_QUESTION_CLASS(aptr); - aptr += QFIXEDSZ; - - /* Display the question, in a format sort of similar to how we will - * display RRs. - */ - printf("\t%-15s.\t", name); - if (dnsclass != C_IN) { - printf("\t%s", class_name(dnsclass)); + if (ares_expand_name(val, val, (int)val_len, &str, &read_len) != + ARES_SUCCESS) { + printf("INVALID!"); + return; } - printf("\t%s\n", type_name(type)); - ares_free_string(name); - return aptr; + + printf("%s.", str); + ares_free_string(str); } -static const unsigned char *display_rr(const unsigned char *aptr, - const unsigned char *abuf, int alen) +static void print_opt_bin(const unsigned char *val, size_t val_len) { - const unsigned char *p; - int type; - int dnsclass; - unsigned int ttl; - int dlen; - int status; - int i; - long len; - int vlen; - char addr[46]; - - union { - unsigned char *as_uchar; - char *as_char; - } name; - - /* Parse the RR name. */ - status = ares_expand_name(aptr, abuf, alen, &name.as_char, &len); - if (status != ARES_SUCCESS) { - return NULL; - } - aptr += len; - - /* Make sure there is enough data after the RR name for the fixed - * part of the RR. - */ - if (aptr + RRFIXEDSZ > abuf + alen) { - ares_free_string(name.as_char); - return NULL; + size_t i; + + for (i = 0; i < val_len; i++) { + printf("%02x", (unsigned int)val[i]); } +} - /* Parse the fixed part of the RR, and advance to the RR data - * field. */ - type = DNS_RR_TYPE(aptr); - dnsclass = DNS_RR_CLASS(aptr); - ttl = DNS_RR_TTL(aptr); - dlen = DNS_RR_LEN(aptr); - aptr += RRFIXEDSZ; - if (aptr + dlen > abuf + alen) { - ares_free_string(name.as_char); - return NULL; +static ares_bool_t adig_isprint(int ch) +{ + if (ch >= 0x20 && ch <= 0x7E) { + return ARES_TRUE; } + return ARES_FALSE; +} - /* Display the RR name, class, and type. */ - printf("\t%-15s.\t%d", name.as_char, ttl); - if (dnsclass != C_IN) { - printf("\t%s", class_name(dnsclass)); +static void print_opt_binp(const unsigned char *val, size_t val_len) +{ + size_t i; + printf("\""); + for (i = 0; i < val_len; i++) { + if (adig_isprint(val[i])) { + printf("%c", val[i]); + } else { + printf("\\%03d", val[i]); + } } - printf("\t%s", type_name(type)); - ares_free_string(name.as_char); - - /* Display the RR data. Don't touch aptr. */ - switch (type) { - case T_CNAME: - case T_MB: - case T_MD: - case T_MF: - case T_MG: - case T_MR: - case T_NS: - case T_PTR: - /* For these types, the RR data is just a domain name. */ - status = ares_expand_name(aptr, abuf, alen, &name.as_char, &len); - if (status != ARES_SUCCESS) { - return NULL; - } - printf("\t%s.", name.as_char); - ares_free_string(name.as_char); - break; + printf("\""); +} - case T_HINFO: - /* The RR data is two length-counted character strings. */ - p = aptr; - len = *p; - if (p + len + 1 > aptr + dlen) { - return NULL; - } - status = ares_expand_string(p, abuf, alen, &name.as_uchar, &len); - if (status != ARES_SUCCESS) { - return NULL; - } - printf("\t%s", name.as_char); - ares_free_string(name.as_char); - p += len; - len = *p; - if (p + len + 1 > aptr + dlen) { - return NULL; - } - status = ares_expand_string(p, abuf, alen, &name.as_uchar, &len); - if (status != ARES_SUCCESS) { - return NULL; - } - printf("\t%s", name.as_char); - ares_free_string(name.as_char); - break; +static void print_opts(const ares_dns_rr_t *rr, ares_dns_rr_key_t key) +{ + size_t i; - case T_MINFO: - /* The RR data is two domain names. */ - p = aptr; - status = ares_expand_name(p, abuf, alen, &name.as_char, &len); - if (status != ARES_SUCCESS) { - return NULL; - } - printf("\t%s.", name.as_char); - ares_free_string(name.as_char); - p += len; - status = ares_expand_name(p, abuf, alen, &name.as_char, &len); - if (status != ARES_SUCCESS) { - return NULL; - } - printf("\t%s.", name.as_char); - ares_free_string(name.as_char); - break; + for (i = 0; i < ares_dns_rr_get_opt_cnt(rr, key); i++) { + size_t val_len = 0; + const unsigned char *val = NULL; + unsigned short opt; + const char *name; - case T_MX: - /* The RR data is two bytes giving a preference ordering, and - * then a domain name. - */ - if (dlen < 2) { - return NULL; - } - printf("\t%d", (int)DNS__16BIT(aptr)); - status = ares_expand_name(aptr + 2, abuf, alen, &name.as_char, &len); - if (status != ARES_SUCCESS) { - return NULL; - } - printf("\t%s.", name.as_char); - ares_free_string(name.as_char); - break; + if (i != 0) { + printf(" "); + } - case T_SOA: - /* The RR data is two domain names and then five four-byte - * numbers giving the serial number and some timeouts. - */ - p = aptr; - status = ares_expand_name(p, abuf, alen, &name.as_char, &len); - if (status != ARES_SUCCESS) { - return NULL; - } - printf("\t%s.\n", name.as_char); - ares_free_string(name.as_char); - p += len; - status = ares_expand_name(p, abuf, alen, &name.as_char, &len); - if (status != ARES_SUCCESS) { - return NULL; - } - printf("\t\t\t\t\t\t%s.\n", name.as_char); - ares_free_string(name.as_char); - p += len; - if (p + 20 > aptr + dlen) { - return NULL; - } - printf("\t\t\t\t\t\t( %u %u %u %u %u )", DNS__32BIT(p), DNS__32BIT(p + 4), - DNS__32BIT(p + 8), DNS__32BIT(p + 12), DNS__32BIT(p + 16)); - break; + opt = ares_dns_rr_get_opt(rr, key, i, &val, &val_len); + name = ares_dns_opt_get_name(key, opt); + if (name == NULL) { + printf("key%u", (unsigned int)opt); + } else { + printf("%s", name); + } + if (val_len == 0) { + return; + } - case T_TXT: - /* The RR data is one or more length-counted character - * strings. */ - p = aptr; - while (p < aptr + dlen) { - len = *p; - if (p + len + 1 > aptr + dlen) { - return NULL; - } - status = ares_expand_string(p, abuf, alen, &name.as_uchar, &len); - if (status != ARES_SUCCESS) { - return NULL; - } - printf("\t%s", name.as_char); - ares_free_string(name.as_char); - p += len; - } - break; + printf("="); + + switch (ares_dns_opt_get_datatype(key, opt)) { + case ARES_OPT_DATATYPE_NONE: + print_opt_none(val, val_len); + break; + case ARES_OPT_DATATYPE_U8_LIST: + print_opt_u8_list(val, val_len); + break; + case ARES_OPT_DATATYPE_INADDR4_LIST: + print_opt_addr_list(val, val_len); + break; + case ARES_OPT_DATATYPE_INADDR6_LIST: + print_opt_addr6_list(val, val_len); + break; + case ARES_OPT_DATATYPE_U16: + case ARES_OPT_DATATYPE_U16_LIST: + print_opt_u16_list(val, val_len); + break; + case ARES_OPT_DATATYPE_U32: + case ARES_OPT_DATATYPE_U32_LIST: + print_opt_u32_list(val, val_len); + break; + case ARES_OPT_DATATYPE_STR_LIST: + print_opt_str_list(val, val_len); + break; + case ARES_OPT_DATATYPE_BIN: + print_opt_bin(val, val_len); + break; + case ARES_OPT_DATATYPE_NAME: + print_opt_name(val, val_len); + break; + } + } +} - case T_CAA: +static void print_addr(const ares_dns_rr_t *rr, ares_dns_rr_key_t key) +{ + const struct in_addr *addr = ares_dns_rr_get_addr(rr, key); + char buf[256] = ""; - p = aptr; + ares_inet_ntop(AF_INET, addr, buf, sizeof(buf)); + printf("%s", buf); +} - /* Flags */ - printf(" %u", (int)*p); - p += 1; +static void print_addr6(const ares_dns_rr_t *rr, ares_dns_rr_key_t key) +{ + const struct ares_in6_addr *addr = ares_dns_rr_get_addr6(rr, key); + char buf[256] = ""; - /* Remainder of record */ - vlen = (int)dlen - ((char)*p) - 2; + ares_inet_ntop(AF_INET6, addr, buf, sizeof(buf)); + printf("%s", buf); +} - /* The Property identifier, one of: - - "issue", - - "iodef", or - - "issuewild" */ - status = ares_expand_string(p, abuf, alen, &name.as_uchar, &len); - if (status != ARES_SUCCESS) { - return NULL; - } - printf(" %s", name.as_char); - ares_free_string(name.as_char); - p += len; +static void print_u8(const ares_dns_rr_t *rr, ares_dns_rr_key_t key) +{ + unsigned char u8 = ares_dns_rr_get_u8(rr, key); + printf("%u", (unsigned int)u8); +} - if (p + vlen > abuf + alen) { - return NULL; - } +static void print_u16(const ares_dns_rr_t *rr, ares_dns_rr_key_t key) +{ + unsigned short u16 = ares_dns_rr_get_u16(rr, key); + printf("%u", (unsigned int)u16); +} - /* A sequence of octets representing the Property Value */ - printf(" %.*s", vlen, p); - break; +static void print_u32(const ares_dns_rr_t *rr, ares_dns_rr_key_t key) +{ + unsigned int u32 = ares_dns_rr_get_u32(rr, key); + printf("%u", u32); +} - case T_A: - /* The RR data is a four-byte Internet address. */ - if (dlen != 4) { - return NULL; - } - printf("\t%s", ares_inet_ntop(AF_INET, aptr, addr, sizeof(addr))); - break; +static void print_name(const ares_dns_rr_t *rr, ares_dns_rr_key_t key) +{ + const char *str = ares_dns_rr_get_str(rr, key); + printf("%s.", str); +} - case T_AAAA: - /* The RR data is a 16-byte IPv6 address. */ - if (dlen != 16) { - return NULL; - } - printf("\t%s", ares_inet_ntop(AF_INET6, aptr, addr, sizeof(addr))); - break; +static void print_str(const ares_dns_rr_t *rr, ares_dns_rr_key_t key) +{ + const char *str = ares_dns_rr_get_str(rr, key); + printf("\"%s\"", str); +} - case T_WKS: - /* Not implemented yet */ - break; +static void print_bin(const ares_dns_rr_t *rr, ares_dns_rr_key_t key) +{ + size_t len = 0; + const unsigned char *binp = ares_dns_rr_get_bin(rr, key, &len); + print_opt_bin(binp, len); +} - case T_SRV: - /* The RR data is three two-byte numbers representing the - * priority, weight, and port, followed by a domain name. - */ +static void print_binp(const ares_dns_rr_t *rr, ares_dns_rr_key_t key) +{ + size_t len; + const unsigned char *binp = ares_dns_rr_get_bin(rr, key, &len); - printf("\t%d", (int)DNS__16BIT(aptr)); - printf(" %d", (int)DNS__16BIT(aptr + 2)); - printf(" %d", (int)DNS__16BIT(aptr + 4)); + print_opt_binp(binp, len); +} - status = ares_expand_name(aptr + 6, abuf, alen, &name.as_char, &len); - if (status != ARES_SUCCESS) { - return NULL; - } - printf("\t%s.", name.as_char); - ares_free_string(name.as_char); - break; +static void print_rr(const ares_dns_rr_t *rr) +{ + const char *name = ares_dns_rr_get_name(rr); + size_t len = strlen(name); + size_t keys_cnt = 0; + ares_dns_rec_type_t rtype = ares_dns_rr_get_type(rr); + const ares_dns_rr_key_t *keys = ares_dns_rr_get_keys(rtype, &keys_cnt); + size_t i; + + printf("%s.\t", name); + if (len < 24) { + printf("\t"); + } + + printf("%u\t%s\t%s\t", ares_dns_rr_get_ttl(rr), + ares_dns_class_tostr(ares_dns_rr_get_class(rr)), + ares_dns_rec_type_tostr(rtype)); + + /* Output params here */ + for (i = 0; i < keys_cnt; i++) { + ares_dns_datatype_t datatype = ares_dns_rr_key_datatype(keys[i]); + if (i != 0) { + printf(" "); + } + + switch (datatype) { + case ARES_DATATYPE_INADDR: + print_addr(rr, keys[i]); + break; + case ARES_DATATYPE_INADDR6: + print_addr6(rr, keys[i]); + break; + case ARES_DATATYPE_U8: + print_u8(rr, keys[i]); + break; + case ARES_DATATYPE_U16: + print_u16(rr, keys[i]); + break; + case ARES_DATATYPE_U32: + print_u32(rr, keys[i]); + break; + case ARES_DATATYPE_NAME: + print_name(rr, keys[i]); + break; + case ARES_DATATYPE_STR: + print_str(rr, keys[i]); + break; + case ARES_DATATYPE_BIN: + print_bin(rr, keys[i]); + break; + case ARES_DATATYPE_BINP: + print_binp(rr, keys[i]); + break; + case ARES_DATATYPE_OPT: + print_opts(rr, keys[i]); + break; + } + } - case T_URI: - /* The RR data is two two-byte numbers representing the - * priority and weight, followed by a target. - */ + printf("\n"); +} - printf("\t%d ", (int)DNS__16BIT(aptr)); - printf("%d \t\t", (int)DNS__16BIT(aptr + 2)); - p = aptr + 4; - for (i = 0; i < dlen - 4; ++i) { - printf("%c", p[i]); - } - break; +static const ares_dns_rr_t *has_opt(ares_dns_record_t *dnsrec, + ares_dns_section_t section) +{ + size_t i; + for (i = 0; i < ares_dns_record_rr_cnt(dnsrec, section); i++) { + const ares_dns_rr_t *rr = ares_dns_record_rr_get(dnsrec, section, i); + if (ares_dns_rr_get_type(rr) == ARES_REC_TYPE_OPT) { + return rr; + } + } + return NULL; +} - case T_NAPTR: +static void print_section(ares_dns_record_t *dnsrec, ares_dns_section_t section) +{ + size_t i; - printf("\t%d", (int)DNS__16BIT(aptr)); /* order */ - printf(" %d\n", (int)DNS__16BIT(aptr + 2)); /* preference */ + if (ares_dns_record_rr_cnt(dnsrec, section) == 0 || + (ares_dns_record_rr_cnt(dnsrec, section) == 1 && + has_opt(dnsrec, section) != NULL)) { + return; + } - p = aptr + 4; - status = ares_expand_string(p, abuf, alen, &name.as_uchar, &len); - if (status != ARES_SUCCESS) { - return NULL; - } - printf("\t\t\t\t\t\t%s\n", name.as_char); - ares_free_string(name.as_char); - p += len; + printf(";; %s SECTION:\n", ares_dns_section_tostr(section)); + for (i = 0; i < ares_dns_record_rr_cnt(dnsrec, section); i++) { + const ares_dns_rr_t *rr = ares_dns_record_rr_get(dnsrec, section, i); + if (ares_dns_rr_get_type(rr) == ARES_REC_TYPE_OPT) { + continue; + } + print_rr(rr); + } + printf("\n"); +} - status = ares_expand_string(p, abuf, alen, &name.as_uchar, &len); - if (status != ARES_SUCCESS) { - return NULL; - } - printf("\t\t\t\t\t\t%s\n", name.as_char); - ares_free_string(name.as_char); - p += len; +static void print_opt_psuedosection(ares_dns_record_t *dnsrec) +{ + const ares_dns_rr_t *rr = has_opt(dnsrec, ARES_SECTION_ADDITIONAL); + if (rr == NULL) { + return; + } - status = ares_expand_string(p, abuf, alen, &name.as_uchar, &len); - if (status != ARES_SUCCESS) { - return NULL; - } - printf("\t\t\t\t\t\t%s\n", name.as_char); - ares_free_string(name.as_char); - p += len; + printf(";; OPT PSEUDOSECTION:\n"); + printf("; EDNS: version: %u, flags: %u; udp: %u\t", + (unsigned int)ares_dns_rr_get_u8(rr, ARES_RR_OPT_VERSION), + (unsigned int)ares_dns_rr_get_u16(rr, ARES_RR_OPT_FLAGS), + (unsigned int)ares_dns_rr_get_u16(rr, ARES_RR_OPT_UDP_SIZE)); - status = ares_expand_name(p, abuf, alen, &name.as_char, &len); - if (status != ARES_SUCCESS) { - return NULL; - } - printf("\t\t\t\t\t\t%s", name.as_char); - ares_free_string(name.as_char); - break; + printf("\n"); +} - case T_DS: - case T_SSHFP: - case T_RRSIG: - case T_NSEC: - case T_DNSKEY: - printf("\t[RR type parsing unavailable]"); - break; +static void callback(void *arg, int status, int timeouts, unsigned char *abuf, + int alen) +{ + ares_dns_record_t *dnsrec = NULL; + (void)arg; + (void)timeouts; - default: - printf("\t[Unknown RR; cannot parse]"); - break; + printf(";; Got answer:"); + if (status != ARES_SUCCESS) { + printf(" %s", ares_strerror(status)); } printf("\n"); - return aptr + dlen; + if (abuf == NULL || alen == 0) { + return; + } + + status = (int)ares_dns_parse(abuf, (size_t)alen, 0, &dnsrec); + if (status != ARES_SUCCESS) { + fprintf(stderr, ";; FAILED TO PARSE DNS PACKET: %s\n", + ares_strerror(status)); + return; + } + + print_header(dnsrec); + print_opt_psuedosection(dnsrec); + print_question(dnsrec); + print_section(dnsrec, ARES_SECTION_ANSWER); + print_section(dnsrec, ARES_SECTION_ADDITIONAL); + print_section(dnsrec, ARES_SECTION_AUTHORITY); + + printf(";; MSG SIZE rcvd: %d\n\n", alen); + ares_dns_record_destroy(dnsrec); } -/* - * With the '-x' (or '-xx') and '-t PTR' options, convert a query for an - * address into a more useful 'T_PTR' type question. - * Like with an input 'query': - * "a.b.c.d" -> "d.c.b.a".in-addr.arpa" for an IPv4 address. - * "a.b.c....x.y.z" -> "z.y.x....c.d.e.IP6.ARPA" for an IPv6 address. - * - * An example from 'dig -x PTR 2001:470:1:1b9::31': - * - * QUESTION SECTION: - * 1.3.0.0.0.0.0.0.0.0.0.0.0.0.0.0.9.b.1.0.1.0.0.0.0.7.4.0.1.0.0.2.IP6.ARPA. IN - * PTR - * - * ANSWER SECTION: - * 1.3.0.0.0.0.0.0.0.0.0.0.0.0.0.0.9.b.1.0.1.0.0.0.0.7.4.0.1.0.0.2.IP6.ARPA. - * 254148 IN PTR ipv6.cybernode.com. - * - * If 'use_bitstring == 1', try to use the more compact RFC-2673 bitstring - * format. Thus the above 'dig' query should become: - * [x13000000000000009b10100007401002].IP6.ARPA. IN PTR - */ -static int convert_query(char **name_p, int use_bitstring) +static ares_status_t enqueue_query(ares_channel_t *channel, + const adig_config_t *config, + const char *name) { -#ifndef MAX_IP6_RR -# define MAX_IP6_RR (16 * sizeof(".x.x") + sizeof(".IP6.ARPA") + 1) -#endif + ares_dns_record_t *dnsrec = NULL; + ares_dns_rr_t *rr = NULL; + ares_status_t status; + unsigned char *buf = NULL; + size_t buf_len = 0; + unsigned short flags = 0; + char *nametemp = NULL; -#ifdef HAVE_INET_PTON -# define ACCEPTED_RETVAL4 1 -# define ACCEPTED_RETVAL6 1 -#else -# define ACCEPTED_RETVAL4 32 -# define ACCEPTED_RETVAL6 128 -#endif + if (!(config->options.flags & ARES_FLAG_NORECURSE)) { + flags |= ARES_FLAG_RD; + } - static char new_name[MAX_IP6_RR]; - static const char hex_chars[] = "0123456789ABCDEF"; - - union { - struct in_addr addr4; - struct ares_in6_addr addr6; - } addr; - - if (ares_inet_pton(AF_INET, *name_p, &addr.addr4) == 1) { - unsigned long laddr = ntohl(addr.addr4.s_addr); - unsigned long a1 = (laddr >> 24UL) & 0xFFUL; - unsigned long a2 = (laddr >> 16UL) & 0xFFUL; - unsigned long a3 = (laddr >> 8UL) & 0xFFUL; - unsigned long a4 = laddr & 0xFFUL; - - snprintf(new_name, sizeof(new_name), "%lu.%lu.%lu.%lu.in-addr.arpa", a4, a3, - a2, a1); - *name_p = new_name; - return (1); + status = ares_dns_record_create(&dnsrec, 0, flags, ARES_OPCODE_QUERY, + ARES_RCODE_NOERROR); + if (status != ARES_SUCCESS) { + goto done; } - if (ares_inet_pton(AF_INET6, *name_p, &addr.addr6) == 1) { - char *c = new_name; - const unsigned char *ip = (const unsigned char *)&addr.addr6; - int max_i = (int)sizeof(addr.addr6) - 1; - int i; - int hi; - int lo; - - /* Use the more compact RFC-2673 notation? - * Currently doesn't work or unsupported by the DNS-servers I've tested - * against. - */ - if (use_bitstring) { - *c++ = '\\'; - *c++ = '['; - *c++ = 'x'; - for (i = max_i; i >= 0; i--) { - hi = ip[i] >> 4; - lo = ip[i] & 15; - *c++ = hex_chars[lo]; - *c++ = hex_chars[hi]; - } - ares_strcpy(c, "].IP6.ARPA", sizeof(new_name) - strlen(c)); - } else { - for (i = max_i; i >= 0; i--) { - hi = ip[i] >> 4; - lo = ip[i] & 15; - *c++ = hex_chars[lo]; - *c++ = '.'; - *c++ = hex_chars[hi]; - *c++ = '.'; - } - ares_strcpy(c, "IP6.ARPA", sizeof(new_name) - strlen(c)); + /* If it is a PTR record, convert from ip address into in-arpa form + * automatically */ + if (config->qtype == ARES_REC_TYPE_PTR) { + struct ares_addr addr; + size_t len; + addr.family = AF_UNSPEC; + + if (ares_dns_pton(name, &addr, &len) != NULL) { + nametemp = ares_dns_addr_to_ptr(&addr); + name = nametemp; } - *name_p = new_name; - return (1); } - printf("Address %s was not legal for this query.\n", *name_p); - return (0); -} -static const char *type_name(int type) -{ - int i; + status = + ares_dns_record_query_add(dnsrec, name, config->qtype, config->qclass); + if (status != ARES_SUCCESS) { + goto done; + } - for (i = 0; i < ntypes; i++) { - if (types[i].value == type) { - return types[i].name; - } + status = ares_dns_record_rr_add(&rr, dnsrec, ARES_SECTION_ADDITIONAL, "", + ARES_REC_TYPE_OPT, ARES_CLASS_IN, 0); + if (status != ARES_SUCCESS) { + goto done; + } + ares_dns_rr_set_u16(rr, ARES_RR_OPT_UDP_SIZE, 1280); + ares_dns_rr_set_u8(rr, ARES_RR_OPT_VERSION, 0); + + status = ares_dns_write(dnsrec, &buf, &buf_len); + if (status != ARES_SUCCESS) { + goto done; } - return "(unknown)"; + + ares_send(channel, buf, (int)buf_len, callback, NULL); + ares_free_string(buf); + +done: + ares_free_string(nametemp); + ares_dns_record_destroy(dnsrec); + return status; } -static const char *class_name(int dnsclass) +int main(int argc, char **argv) { - int i; + ares_channel_t *channel = NULL; + ares_status_t status; + adig_config_t config; + int i; + int rv = 0; - for (i = 0; i < nclasses; i++) { - if (classes[i].value == dnsclass) { - return classes[i].name; +#ifdef USE_WINSOCK + WORD wVersionRequested = MAKEWORD(USE_WINSOCK, USE_WINSOCK); + WSADATA wsaData; + WSAStartup(wVersionRequested, &wsaData); +#endif + + status = (ares_status_t)ares_library_init(ARES_LIB_INIT_ALL); + if (status != ARES_SUCCESS) { + fprintf(stderr, "ares_library_init: %s\n", ares_strerror((int)status)); + return 1; + } + + memset(&config, 0, sizeof(config)); + config.qclass = ARES_CLASS_IN; + config.qtype = ARES_REC_TYPE_A; + if (!read_cmdline(argc, (const char **)argv, &config)) { + printf("%s\n", config.error); + print_help(); + rv = 1; + goto done; + } + + if (config.is_help) { + print_help(); + goto done; + } + + status = + (ares_status_t)ares_init_options(&channel, &config.options, config.optmask); + if (status != ARES_SUCCESS) { + fprintf(stderr, "ares_init_options: %s\n", ares_strerror((int)status)); + rv = 1; + goto done; + } + + if (config.servers) { + status = (ares_status_t)ares_set_servers_ports_csv(channel, config.servers); + if (status != ARES_SUCCESS) { + fprintf(stderr, "ares_set_servers_ports_csv: %s\n", + ares_strerror((int)status)); + rv = 1; + goto done; } } - return "(unknown)"; -} -static void usage(void) -{ - fprintf(stderr, "usage: adig [-h] [-d] [-f flag] [-s server] [-c class] " - "[-t type] [-T|U port] [-x|-xx] name ...\n"); - exit(1); -} + /* Enqueue a query for each separate name */ + for (i = config.args_processed; i < argc; i++) { + status = enqueue_query(channel, &config, argv[i]); + if (status != ARES_SUCCESS) { + fprintf(stderr, "Failed to create query for %s: %s\n", argv[i], + ares_strerror((int)status)); + rv = 1; + goto done; + } + } -static void destroy_addr_list(struct ares_addr_node *head) -{ - while (head) { - struct ares_addr_node *detached = head; - head = head->next; - free(detached); + /* Debug */ + printf("\n; <<>> c-ares DiG %s <<>>", ares_version(NULL)); + for (i = config.args_processed; i < argc; i++) { + printf(" %s", argv[i]); } -} + printf("\n"); -static void append_addr_list(struct ares_addr_node **head, - struct ares_addr_node *node) -{ - struct ares_addr_node *last; - node->next = NULL; - if (*head) { - last = *head; - while (last->next) { - last = last->next; + while (1) { + fd_set read_fds; + fd_set write_fds; + int nfds; + struct timeval tv; + struct timeval *tvp; + int count; + + FD_ZERO(&read_fds); + FD_ZERO(&write_fds); + memset(&tv, 0, sizeof(tv)); + + nfds = ares_fds(channel, &read_fds, &write_fds); + if (nfds == 0) { + break; + } + tvp = ares_timeout(channel, NULL, &tv); + if (tvp == NULL) { + break; + } + count = select(nfds, &read_fds, &write_fds, NULL, tvp); + if (count < 0) { +#ifdef USE_WINSOCK + int err = WSAGetLastError(); +#else + int err = errno; +#endif + if (err != EAGAIN && err != EINTR) { + fprintf(stderr, "select fail: %d", err); + rv = 1; + goto done; + } } - last->next = node; - } else { - *head = node; + ares_process(channel, &read_fds, &write_fds); } -} -/* Information from the man page. Formatting taken from man -h */ -static void print_help_info_adig(void) -{ - printf("adig, version %s\n\n", ARES_VERSION_STR); - printf( - "usage: adig [-h] [-d] [-f flag] [[-s server] ...] [-T|U port] [-c class] " - "[-t type] [-x|-xx] name ...\n\n" - " h : Display this help and exit.\n" - " d : Print some extra debugging output.\n\n" - " f flag : Add a behavior control flag. Possible values are\n" - " igntc - ignore to query in TCP to get truncated UDP " - "answer,\n" - " noaliases - don't honor the HOSTALIASES environment " - "variable,\n" - " norecurse - don't query upstream servers recursively,\n" - " primary - use the first server,\n" - " stayopen - don't close the communication sockets, and\n" - " usevc - use TCP only.\n" - " s server : Connect to the specified DNS server, instead of the system's " - "default one(s).\n" - " Servers are tried in round-robin, if the previous one " - "failed.\n" - " T port : Connect to the specified TCP port of DNS server.\n" - " U port : Connect to the specified UDP port of DNS server.\n" - " c class : Set the query class. Possible values for class are ANY, " - "CHAOS, HS and IN (default)\n" - " t type : Query records of the specified type.\n" - " Possible values for type are A (default), AAAA, AFSDB, ANY, " - "AXFR,\n" - " CNAME, GPOS, HINFO, ISDN, KEY, LOC, MAILA, MAILB, MB, MD, " - "MF, MG,\n" - " MINFO, MR, MX, NAPTR, NS, NSAP, NSAP_PTR, NULL, PTR, PX, " - "RP, RT,\n" - " SIG, SOA, SRV, TXT, URI, WKS and X25.\n\n" - " -x : For a '-t PTR a.b.c.d' lookup, query for 'd.c.b.a.in-addr.arpa.'\n" - " -xx : As above, but for IPv6, compact the format into a bitstring like\n" - " '[xabcdef00000000000000000000000000].IP6.ARPA.'\n"); - exit(0); +done: + free_config(&config); + ares_destroy(channel); + ares_library_cleanup(); + +#ifdef USE_WINSOCK + WSACleanup(); +#endif + return rv; } diff --git a/src/tools/ahost.c b/src/tools/ahost.c index 7f133969..1f293fc6 100644 --- a/src/tools/ahost.c +++ b/src/tools/ahost.c @@ -76,6 +76,7 @@ int main(int argc, char **argv) struct timeval tv; struct in_addr addr4; struct ares_in6_addr addr6; + ares_getopt_state_t state; #ifdef USE_WINSOCK WORD wVersionRequested = MAKEWORD(USE_WINSOCK, USE_WINSOCK); @@ -91,7 +92,8 @@ int main(int argc, char **argv) return 1; } - while ((c = ares_getopt(argc, argv, "dt:h?s:")) != -1) { + ares_getopt_init(&state, argc, (const char **)argv); + while ((c = ares_getopt(&state, "dt:h?s:")) != -1) { switch (c) { case 'd': #ifdef WATT32 @@ -103,14 +105,14 @@ int main(int argc, char **argv) options.ndomains++; options.domains = (char **)realloc( options.domains, (size_t)options.ndomains * sizeof(char *)); - options.domains[options.ndomains - 1] = strdup(optarg); + options.domains[options.ndomains - 1] = strdup(state.optarg); break; case 't': - if (!strcasecmp(optarg, "a")) { + if (!strcasecmp(state.optarg, "a")) { addr_family = AF_INET; - } else if (!strcasecmp(optarg, "aaaa")) { + } else if (!strcasecmp(state.optarg, "aaaa")) { addr_family = AF_INET6; - } else if (!strcasecmp(optarg, "u")) { + } else if (!strcasecmp(state.optarg, "u")) { addr_family = AF_UNSPEC; } else { usage(); @@ -128,8 +130,8 @@ int main(int argc, char **argv) } } - argc -= optind; - argv += optind; + argc -= state.optind; + argv += state.optind; if (argc < 1) { usage(); } diff --git a/src/tools/ares_getopt.c b/src/tools/ares_getopt.c index b49606fa..6f147832 100644 --- a/src/tools/ares_getopt.c +++ b/src/tools/ares_getopt.c @@ -50,77 +50,79 @@ #include #include "ares_getopt.h" -int opterr = 1; /* if error message should be printed */ -int optind = 1; /* index into parent argv vector */ -int optopt = 0; /* character checked for validity */ -static int optreset; /* reset getopt */ -char *optarg; /* argument associated with option */ - #define BADCH (int)'?' #define BADARG (int)':' #define EMSG (char *)"" +void ares_getopt_init(ares_getopt_state_t *state, int nargc, const char **nargv) +{ + memset(state, 0, sizeof(*state)); + state->opterr = 1; + state->optind = 1; + state->place = EMSG; + state->argc = nargc; + state->argv = nargv; +} + /* * ares_getopt -- * Parse argc/argv argument vector. */ -int ares_getopt(int nargc, char * const nargv[], const char *ostr) +int ares_getopt(ares_getopt_state_t *state, const char *ostr) { - static char *place = EMSG; /* option letter processing */ - char *oli; /* option letter list index */ + char *oli; /* option letter list index */ - if (optreset || !*place) { /* update scanning pointer */ - optreset = 0; - if (optind >= nargc || *(place = nargv[optind]) != '-') { - place = EMSG; + if (!*state->place) { /* update scanning pointer */ + if (state->optind >= state->argc || *(state->place = state->argv[state->optind]) != '-') { + state->place = EMSG; return (EOF); } - if (place[1] && *++place == '-') { /* found "--" */ - ++optind; - place = EMSG; + if (state->place[1] && *++state->place == '-') { /* found "--" */ + ++state->optind; + state->place = EMSG; return (EOF); } } /* option letter okay? */ - if ((optopt = (int)*place++) == (int)':' || - (oli = strchr(ostr, optopt)) == NULL) { + if ((state->optopt = (int)*state->place++) == (int)':' || + (oli = strchr(ostr, state->optopt)) == NULL) { /* * if the user didn't specify '-' as an option, * assume it means EOF. */ - if (optopt == (int)'-') { + if (state->optopt == (int)'-') { return (EOF); } - if (!*place) { - ++optind; + if (!*state->place) { + ++state->optind; } - if (opterr && *ostr != ':') { - (void)fprintf(stderr, "%s: illegal option -- %c\n", __FILE__, optopt); + if (state->opterr && *ostr != ':') { + (void)fprintf(stderr, "%s: illegal option -- %c\n", __FILE__, state->optopt); } return (BADCH); } if (*++oli != ':') { /* don't need argument */ - optarg = NULL; - if (!*place) { - ++optind; + state->optarg = NULL; + if (!*state->place) { + ++state->optind; } } else { /* need an argument */ - if (*place) { /* no white space */ - optarg = place; - } else if (nargc <= ++optind) { /* no arg */ - place = EMSG; + if (*state->place) { /* no white space */ + state->optarg = state->place; + } else if (state->argc <= ++state->optind) { /* no arg */ + state->place = EMSG; if (*ostr == ':') { return (BADARG); } - if (opterr) { + if (state->opterr) { (void)fprintf(stderr, "%s: option requires an argument -- %c\n", - __FILE__, optopt); + __FILE__, state->optopt); } return (BADCH); } else { /* white space */ - optarg = nargv[optind]; + state->optarg = state->argv[state->optind]; } - place = EMSG; - ++optind; + state->place = EMSG; + ++state->optind; } - return (optopt); /* dump back option letter */ + return (state->optopt); /* dump back option letter */ } diff --git a/src/tools/ares_getopt.h b/src/tools/ares_getopt.h index b80da752..d25d0b93 100644 --- a/src/tools/ares_getopt.h +++ b/src/tools/ares_getopt.h @@ -33,33 +33,17 @@ */ -int ares_getopt(int nargc, char * const nargv[], const char *ostr); +typedef struct { + const char *optarg; /* argument associated with option */ + int optind; /* index into parent argv vector */ + int opterr; /* if error message should be printed */ + int optopt; /* character checked for validity */ + const char *place; + int argc; + const char **argv; +} ares_getopt_state_t; -#ifdef optarg -# undef optarg -#endif -#ifdef optind -# undef optind -#endif -#ifdef opterr -# undef opterr -#endif -#ifdef optopt -# undef optopt -#endif -#ifdef optreset -# undef optreset -#endif - -#define optarg ares_optarg -#define optind ares_optind -#define opterr ares_opterr -#define optopt ares_optopt -#define optreset ares_optreset - -extern char *optarg; -extern int optind; -extern int opterr; -extern int optopt; +void ares_getopt_init(ares_getopt_state_t *state, int argc, const char **argv); +int ares_getopt(ares_getopt_state_t *state, const char *ostr); #endif /* ARES_GETOPT_H */ diff --git a/test/ares-test-internal.cc b/test/ares-test-internal.cc index fe55110c..ef26d463 100644 --- a/test/ares-test-internal.cc +++ b/test/ares-test-internal.cc @@ -621,14 +621,19 @@ TEST_F(LibraryTest, DNSRecord) { ares_dns_rr_set_u16(rr, ARES_RR_SVCB_PRIORITY, 1)); EXPECT_EQ(ARES_SUCCESS, ares_dns_rr_set_str(rr, ARES_RR_SVCB_TARGET, "svc1.example.net")); - const unsigned char svcb_ipv6hint[] = "2001:db8::1"; + /* IPV6 hint is a list of IPV6 addresses in network byte order, concatenated */ + struct ares_addr svcb_addr; + svcb_addr.family = AF_UNSPEC; + size_t svcb_ipv6hint_len = 0; + const unsigned char *svcb_ipv6hint = (const unsigned char *)ares_dns_pton("2001:db8::1", &svcb_addr, &svcb_ipv6hint_len); EXPECT_EQ(ARES_SUCCESS, ares_dns_rr_set_opt(rr, ARES_RR_SVCB_PARAMS, ARES_SVCB_PARAM_IPV6HINT, - svcb_ipv6hint, sizeof(svcb_ipv6hint)-1)); - const unsigned char svcb_port[] = "1234"; + svcb_ipv6hint, svcb_ipv6hint_len)); + /* Port is 16bit big endian format */ + unsigned short svcb_port = htons(1234); EXPECT_EQ(ARES_SUCCESS, - ares_dns_rr_set_opt(rr, ARES_RR_SVCB_PARAMS, ARES_SVCB_PARAM_IPV6HINT, - svcb_port, sizeof(svcb_port)-1)); + ares_dns_rr_set_opt(rr, ARES_RR_SVCB_PARAMS, ARES_SVCB_PARAM_PORT, + (const unsigned char *)&svcb_port, sizeof(svcb_port))); /* HTTPS */ EXPECT_EQ(ARES_SUCCESS, ares_dns_record_rr_add(&rr, dnsrec, ARES_SECTION_ADDITIONAL, @@ -637,10 +642,12 @@ TEST_F(LibraryTest, DNSRecord) { ares_dns_rr_set_u16(rr, ARES_RR_HTTPS_PRIORITY, 1)); EXPECT_EQ(ARES_SUCCESS, ares_dns_rr_set_str(rr, ARES_RR_HTTPS_TARGET, "")); - const unsigned char https_alpn[] = "h3"; + + /* In DNS string format which is 1 octet length indicator followed by string */ + const unsigned char https_alpn[] = { 0x02, 'h', '3' }; EXPECT_EQ(ARES_SUCCESS, ares_dns_rr_set_opt(rr, ARES_RR_HTTPS_PARAMS, ARES_SVCB_PARAM_ALPN, - https_alpn, sizeof(https_alpn)-1)); + https_alpn, sizeof(https_alpn))); /* URI */ EXPECT_EQ(ARES_SUCCESS, ares_dns_record_rr_add(&rr, dnsrec, ARES_SECTION_ADDITIONAL,