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