Getting and parsing a HTTPS record
Brad House
brad at brad-house.com
Thu Jan 16 14:50:45 CET 2025
Heh, I hate writing docs ... so, I made a few huge manpages. If you
have someone that loves writing docs to help out, send them this way :)
I wouldn't recommend using ares_dns_record_create() as you have to jump
through a lot of hoops to do things properly, like setting up EDNS and
whatnot. Just use ares_query_dnsrec() which does the heavy lifting for you.
The worst part in all honesty is the 'option' syntax, as HTTPS/SVCB have
dynamic option/parameter records that can be attached, and can be in any
format. I extracted some code from adig for printing those. I'm not
sure if you need all the data or if you're just looking for some certain
data. The current/known params are:
/*! SVCB (and HTTPS) RR known parameters */
typedef enum {
/*! Mandatory keys in this RR (RFC 9460 Section 8) */
ARES_SVCB_PARAM_MANDATORY = 0,
/*! Additional supported protocols (RFC 9460 Section 7.1) */
ARES_SVCB_PARAM_ALPN = 1,
/*! No support for default protocol (RFC 9460 Section 7.1) */
ARES_SVCB_PARAM_NO_DEFAULT_ALPN = 2,
/*! Port for alternative endpoint (RFC 9460 Section 7.2) */
ARES_SVCB_PARAM_PORT = 3,
/*! IPv4 address hints (RFC 9460 Section 7.3) */
ARES_SVCB_PARAM_IPV4HINT = 4,
/*! RESERVED (held for Encrypted ClientHello) */
ARES_SVCB_PARAM_ECH = 5,
/*! IPv6 address hints (RFC 9460 Section 7.3) */
ARES_SVCB_PARAM_IPV6HINT = 6
} ares_svcb_param_t;
See my attached example, compile/run like:
cc -I/usr/local/include -Wall -o ares-https ares-https.c -Wl,-rpath
/usr/local/lib -lcares
./ares-https www.cloudflare.com
Result: Successful completion, timeouts: 0
HTTPS Priority: 1
HTTPS Target:
HTTPS Params:
alpn(1)="h3, h2"
ipv4hint(4)=104.16.123.96, 104.16.124.96
ipv6hint(6)=2606:4700::6810:7b60, 2606:4700::6810:7c60
-Brad
On 1/16/25 3:05 AM, Daniel Stenberg via c-ares wrote:
> Hi,
>
> I want is to create a request for a HTTPS record and get the answer or
> an error. I struggle to understand how to do this. The
> ares_dns_record_create is hard to follow and understand what sequence
> to use and how to glue everything together.
>
> I think the docs would benefit from being split up to document one
> function per man page as God intended. With examples showing how they
> can be used.
>
> This is my initial attempt to send off the HTTPS RR request:
>
> ares_dns_record_create(&dnsrec, 0 /* id */, 0, /* flags */
> ARES_OPCODE_QUERY, ARES_RCODE_NOERROR);
> ares_dns_record_query_add(dnsrec, hostname,
> ARES_REC_TYPE_HTTPS, ARES_CLASS_IN);
> ares_send_dnsrec((ares_channel)resolver_hgandle,
> dnsrec, dnsrec_done_cb, data, NULL);
>
> But I simply cannot figure out how the dnsrec_done_cb callback should
> be written to parse the incoming reply?
>
> What helpers should I use?
>
-------------- next part --------------
#include <stdio.h>
#include <string.h>
#include <ares.h>
static void print_opt_none(const unsigned char *val, size_t val_len)
{
(void)val;
if (val_len != 0) {
printf("INVALID!");
}
}
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);
}
}
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] = "";
ares_inet_ntop(AF_INET6, val + i, buf, sizeof(buf));
if (i != 0) {
printf(", ");
}
printf("%s", buf);
}
}
static void print_opt_u8_list(const unsigned char *val, size_t val_len)
{
size_t i;
for (i = 0; i < val_len; i++) {
if (i != 0) {
printf(", ");
}
printf("%u", (unsigned int)val[i]);
}
}
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);
}
}
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;
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);
}
}
static void print_opt_str_list(const unsigned char *val, size_t val_len)
{
size_t cnt = 0;
printf("\"");
while (val_len) {
long read_len = 0;
unsigned char *str = NULL;
ares_status_t status;
if (cnt) {
printf(", ");
}
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 void print_opt_name(const unsigned char *val, size_t val_len)
{
char *str = NULL;
long read_len = 0;
if (ares_expand_name(val, val, (int)val_len, &str, &read_len) !=
ARES_SUCCESS) {
printf("INVALID!");
return;
}
printf("%s.", str);
ares_free_string(str);
}
static void print_opt_bin(const unsigned char *val, size_t val_len)
{
size_t i;
for (i = 0; i < val_len; i++) {
printf("%02x", (unsigned int)val[i]);
}
}
static void print_opt(const ares_dns_rr_t *rr, ares_dns_rr_key_t key, size_t idx)
{
size_t val_len = 0;
const unsigned char *val = NULL;
unsigned short opt;
const char *name;
opt = ares_dns_rr_get_opt(rr, key, idx, &val, &val_len);
name = ares_dns_opt_get_name(key, opt);
if (name == NULL) {
printf("\tkey%u", (unsigned int)opt);
} else {
printf("\t%s(%u)", name, (unsigned int)opt);
}
if (val_len == 0) {
return;
}
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;
}
printf("\n");
}
static void dnsrec_cb(void *arg, ares_status_t status, size_t timeouts,
const ares_dns_record_t *dnsrec)
{
size_t i;
const ares_dns_rr_t *rr = NULL;
(void)arg; /* Example does not use user context */
printf("Result: %s, timeouts: %zu\n", ares_strerror(status), timeouts);
if (dnsrec == NULL) {
return;
}
for (i=0; i<ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_ANSWER); i++) {
size_t opt;
rr = ares_dns_record_rr_get_const(dnsrec, ARES_SECTION_ANSWER, i);
if (ares_dns_rr_get_type(rr) != ARES_REC_TYPE_HTTPS) {
continue;
}
printf("HTTPS Priority: %u\n", ares_dns_rr_get_u16(rr, ARES_RR_HTTPS_PRIORITY));
printf("HTTPS Target: %s.\n", ares_dns_rr_get_str(rr, ARES_RR_HTTPS_TARGET));
printf("HTTPS Params:\n");
for (opt=0; opt<ares_dns_rr_get_opt_cnt(rr, ARES_RR_HTTPS_PARAMS); opt++) {
print_opt(rr, ARES_RR_HTTPS_PARAMS, opt);
}
}
}
int main(int argc, char **argv)
{
ares_channel_t *channel = NULL;
struct ares_options options;
int optmask = 0;
ares_status_t status;
if (argc != 2) {
printf("Usage: %s domain\n", argv[0]);
return 1;
}
/* Initialize library */
ares_library_init(ARES_LIB_INIT_ALL);
if (!ares_threadsafety()) {
printf("c-ares not compiled with thread support\n");
return 1;
}
/* Enable event thread so we don't have to monitor file descriptors */
memset(&options, 0, sizeof(options));
optmask |= ARES_OPT_EVENT_THREAD;
options.evsys = ARES_EVSYS_DEFAULT;
/* Initialize channel to run queries, a single channel can accept unlimited
* queries */
status = ares_init_options(&channel, &options, optmask);
if (status != ARES_SUCCESS) {
printf("c-ares initialization issue: %s\n", ares_strerror(status));
return 1;
}
/* Perform query */
status = ares_query_dnsrec(channel, argv[1], ARES_CLASS_IN,
ARES_REC_TYPE_HTTPS, dnsrec_cb,
NULL /* user context */, NULL /* qid */);
if (status != ARES_SUCCESS) {
printf("failed to enqueue query: %s\n", ares_strerror(status));
return 1;
}
/* Wait until no more requests are left to be processed */
ares_queue_wait_empty(channel, -1);
/* Cleanup */
ares_destroy(channel);
ares_library_cleanup();
return 0;
}
More information about the c-ares
mailing list