DNS-over-HTTPS(DoH)详解与C/C++代码实现

HTTPS上的DNS(DoH)是一种相对较新的协议,通过超文本传输协议安全加密会话传递DNS查询来加密域名系统流量。DoH试图通过隐藏DNS查询来改善在线隐私。

DoH的工作原理与DNS类似,但HTTPS会话保留请求并最大限度地减少查询期间交换的信息。网络浏览器,如Mozilla的Firefox、微软的Edge和谷歌的Chrome,都具有使用加密DoH的功能,目的是提高用户的数据隐私和安全性。

什么是DNS

域名系统,或称DNS,是一种在互联网上将域名转换为IP地址的协议。当您在浏览器中键入域名时,域名系统(DNS)会将其转换为IP地址。

DNS系统允许您通过记住网站和其他在线服务使用的域名而不是数字互联网协议地址来连接网站和其他联机服务。

HTTPS协议

HTTPS是一种用于保护互联网通信安全的协议。HTTPS由网景公司开发,并于1994年引入,作为对HTTP(超文本传输协议)的增强。

它提供端到端的数据加密,这意味着当你访问网站时,没有人可以看到你的个人信息。加密连接可确保密码和信用卡号等敏感数据的安全。

既然我们都知道HTTPS的作用,让我们更深入地了解一下为什么这是一件如此重要的事情。

如何在浏览器中启用DoH?

大多数流行的浏览器默认情况下都启用了DoH,但您可以按照以下步骤进行检查。

转到“设置”

然后是“隐私与安全”

在“隐私和安全”部分,将有“安全”

向下滚动,您将看到“使用安全DNS”

从那里,选择“使用自定义”而不是“使用您当前的服务提供商”,因为大多数ISP的解析器速度很慢,有时会停机。

选择您选择的DNS解析程序,您可以选择Cloudflare DNS、Google DNS、OpenDNS或CleanBrowsing。
在这里插入图片描述

DoH是如何工作

为了了解DoH是如何工作的,有必要首先了解常规DNS是如何工作。网站托管在网络服务器上,每个网络服务器或服务器上的网站都有一个相关的互联网协议(IP)地址。浏览器要访问网站,必须首先确定网站的IP地址,这就是DNS的重要位置。DNS服务器的工作是转换主机名,例如https://whatis.com,转换为IP地址。

当用户在浏览器中输入主机名时,请求会被发送到递归解析程序,然后递归解析程序会将请求传递到根名称服务器——如果解析程序还不知道如何解析查询的话。根名称服务器处理顶级域,如.com、.org和.edu。然后根服务器将相应的顶级DNS服务器的地址发送回解析程序。例如,如果用户试图访问.com网站,则根DNS将提供与.com顶级域服务器相关联的地址。

此时,解析程序将其请求发送到顶级域服务器,顶级域服务器使用处理请求域的DNS服务器的IP地址进行响应。然后,解析程序将请求发送到此DNS服务器,该服务器返回用户试图访问的网站的IP地址。然后,浏览器能够向该IP地址发出HTTP或HTTPS请求,以访问用户请求的网站。在某些情况下,缓存使这个过程成为快捷方式,但这是正常DNS工作的本质。

DOH的工作方式基本相同,但有两个关键区别。第一个也是最明显的区别是,DNS请求被封装在HTTPS会话中,而不是像过去那样由浏览器发出HTTP请求。与HTTPS网络流量一样,这些请求是通过端口443发送的。值得注意的是,为了让DoH工作,浏览器和DNS服务器都必须支持DoH。

标准DNS和DoH之间的另一个关键区别是,DoH寻求最小化在各种DNS查询期间传输的信息。它只传输完成名称解析过程中当前步骤所需的域名部分,而不是发送用户浏览器试图解析的完整域名。例如,DNS根不需要知道用户的浏览器正在尝试解析 https://whatis.com.它只需要知道浏览器正在尝试解析.com地址。

使用DOH的好处

最主要就是防止“中间人”攻击。

HTTPS上的DNS是一种新的协议,它用加密和验证的版本取代了传统的DNS协议。这一变化的结果是,您的浏览将更加安全和私密。

DoH使用HTTPS,这意味着您的设备和DNS服务器之间的所有通信都是端到端加密的,这意味着您在没有从通信通道两侧检测到的情况下,任何第三方都无法看到您在网上做什么或篡改任何来回发送的数据。

这可以防止“中间人”攻击拦截和更改您与网站之间发送的敏感数据。

增强隐私

默认情况下,当您访问网站时,浏览器会将有关网站的信息发送到DNS服务器。您的ISP使用这些信息来确定将您发送到哪个服务器。

使用HTTPS上的DNS,您的浏览器将直接向DoH服务器发送对网站地址的请求,而不是通过您的ISP。这意味着您的ISP将不会掌握您正在访问的网站的任何信息,也不能将这些数据出售给广告商或其他第三方。

DNS Over TLS(DoT)和DNS Over HTTPS(DoH)之间的区别是什么?

DoT与DoH的相似之处在于,它还使用TLS的安全传输层。这将在您和DNS服务器之间创建加密连接。

然而,DoH对通过端口443(与常规网络流量使用的端口相同)发出的网站请求进行加密。而DoT仅使用端口853。

从隐私角度来看,DoH是更好的选择。由于端口443通常由网络管理员解除阻止。而使用DoT使用非标准端口,这使得它不太可能在公共网络或工作场所访问。

DNS-over-HTTPS(DoH)C/C++代码实现

...
typedef enum {
  DOH_OK,
  DOH_DNS_BAD_LABEL,    /* 1 */
  DOH_DNS_OUT_OF_RANGE, /* 2 */
  DOH_DNS_CNAME_LOOP,   /* 3 */
  DOH_TOO_SMALL_BUFFER, /* 4 */
  DOH_OUT_OF_MEM,       /* 5 */
  DOH_DNS_RDATA_LEN,    /* 6 */
  DOH_DNS_MALFORMAT,    /* 7 - wrong size or bad ID */
  DOH_DNS_BAD_RCODE,    /* 8 - no such name */
  DOH_DNS_UNEXPECTED_TYPE,  /* 9 */
  DOH_DNS_UNEXPECTED_CLASS, /* 10 */
  DOH_NO_CONTENT            /* 11 */
} DOHcode;

struct dnsentry {
  unsigned int ttl;
  int numv4;
  unsigned int v4addr[MAX_ADDR];
  int numv6;
  struct addr6 v6addr[MAX_ADDR];
  int numcname;
  struct cnamestore cname[MAX_ADDR];
  int numtxt;
  struct txtstore txt[MAX_ADDR];
};

static const char *type2name(int dnstype)
{
  switch(dnstype) {
  case DNS_TYPE_A: return "A";
  case DNS_TYPE_NS: return "NS";
  case DNS_TYPE_CNAME: return "CNAME";
  case DNS_TYPE_TXT: return "TXT";
  case DNS_TYPE_AAAA: return "AAAA";
  }
  return "Unknown";
}

...

static size_t doh_encode(const char *host,
                         int dnstype,
                         unsigned char *dnsp, /* buffer */
                         size_t len) /* buffer size */
{
...

  *dnsp++ = 0; /* 16 bit id */
  *dnsp++ = 0;
  *dnsp++ = 0x01; /* |QR|   Opcode  |AA|TC|RD| Set the RD bit */
  *dnsp++ = '\0'; /* |RA|   Z    |   RCODE   |                */
  *dnsp++ = '\0';
  *dnsp++ = 1;    /* QDCOUNT (number of entries in the question section) */
  *dnsp++ = '\0';
  *dnsp++ = '\0'; /* ANCOUNT */
  *dnsp++ = '\0';
  *dnsp++ = '\0'; /* NSCOUNT */
  *dnsp++ = '\0';
  *dnsp++ = '\0'; /* ARCOUNT */

...
  do 
  {
    char *dot = strchr(hostp, '.');
    size_t labellen;
    bool found = false;
    if(dot) 
    {
      found = true;
      labellen = dot - hostp;
    }
    else
      labellen = strlen(hostp);
    if(labellen > 63)
      return DOH_DNS_BAD_LABEL;
      
    *dnsp++ = (unsigned char)labellen;
    memcpy(dnsp, hostp, labellen);
    dnsp += labellen;
    hostp += labellen + 1;
    if(!found) 
    {
      *dnsp++ = 0; 
      break;
    }
  } while(1);

...
  return dnsp - orig;
}

static DOHcode store_cname(unsigned char *doh,
                           size_t dohlen,
                           unsigned int index,
                           struct dnsentry *d)
{
  struct cnamestore *c = &d->cname[d->numcname++];
  unsigned int loop = 128; /* 一个有效的DNS名称永远不会循环这么多 */
  unsigned char length;
  do 
  {
    if(index >= dohlen)
      return DOH_DNS_OUT_OF_RANGE;
    length = doh[index];
    if((length & 0xc0) == 0xc0) 
    {
      unsigned short newpos;
      /* 名称指针,获取新的偏移量(14位) */
      if((index + 1) >= dohlen)
        return DOH_DNS_OUT_OF_RANGE;
      /* 移动到新索引  */
      newpos = (length & 0x3f) << 8 | doh[index+1];
      index = newpos;
      continue;
    }
    else if(length & 0xc0)
      return DOH_DNS_BAD_LABEL; 
    else
      index++;

...

  if(!loop)
    return DOH_DNS_CNAME_LOOP;
  return DOH_OK;
}

static DOHcode rdata(unsigned char *doh,
                     size_t dohlen,
                     unsigned short rdlength,
                     unsigned short type,
                     int index,
                     struct dnsentry *d)
{
  /* RDATA
     - A (TYPE 1):  4 bytes
     - AAAA (TYPE 28): 16 bytes
     - NS (TYPE 2): N bytes */
  DOHcode rc;

  switch(type) 
  {
  case DNS_TYPE_A:
    if(rdlength != 4)
      return DOH_DNS_RDATA_LEN;
    rc = store_a(doh, index, d);
    if(rc)
      return rc;
    break;
  case DNS_TYPE_AAAA:
    if(rdlength != 16)
      return DOH_DNS_RDATA_LEN;
    rc = store_aaaa(doh, index, d);
    if(rc)
      return rc;
    break;
  case DNS_TYPE_CNAME:
    rc = store_cname(doh, dohlen, index, d);
    if(rc)
      return rc;
    break;
  case DNS_TYPE_TXT:
    if(rdlength <= 1)
      return DOH_DNS_RDATA_LEN;
    rc = store_txt(doh, dohlen, rdlength, index, d);
    if(rc)
      return rc;
    break;
  default:
    break;
  }
  return DOH_OK;
}
...

int main(int argc, char **argv)
{
...

  if(argc < 1)
    help("no lookup name specified");
  argc--; host = *argv++;
  while(argc > 0) 
  {
    if(n_urls == MAX_URLS)
      help("too many URLs");
    argc--; urls[n_urls++] = *argv++;
  }
  if(n_urls == 0)
    urls[n_urls++] = default_url;
  if(n_urls > 1 && !test_mode)
    help("multiple URLS only permitted in test mode");

  curl_global_init(CURL_GLOBAL_ALL);

  /* 使用旧的内容类型 */
  headers = curl_slist_append(headers,
                              "Content-Type: application/dns-message");
  headers = curl_slist_append(headers,
                              "Accept: application/dns-message");

  /* 初始化多堆栈 */
  multi = curl_multi_init();

  doh_init(&d);

  for(i = 0; i < n_urls; i++) 
  {
    if(query_type == 0 || query_type == DNS_TYPE_A) 
    {
      rc = initprobe(DNS_TYPE_A, host, urls[i], multi,
                     trace_enabled, headers, insecure_mode,
                     transport, resolve, &config);
      if(rc != 0) 
      {
        fprintf(stderr, "initprobe() failed (DNS_TYPE_A)\n");
        exit(1);
      }
    }
    if(query_type == 0 || query_type == DNS_TYPE_AAAA) 
    {
      rc = initprobe(DNS_TYPE_AAAA, host, urls[i], multi,
                     trace_enabled, headers, insecure_mode,
                     transport, resolve, &config);
      if(rc != 0) 
      {
        fprintf(stderr, "initprobe() failed (DNS_TYPE_AAAA)\n");
        exit(1);
      }
    }
    if(query_type == DNS_TYPE_TXT) 
    {
      rc = initprobe(DNS_TYPE_TXT, host, urls[i], multi,
                     trace_enabled, headers, insecure_mode,
                     transport, resolve, &config);
      if(rc != 0) 
      {
        fprintf(stderr, "initprobe() failed (DNS_TYPE_TXT)\n");
        exit(1);
      }
    }
    if(query_type == DNS_TYPE_CNAME) 
    {
      rc = initprobe(DNS_TYPE_CNAME, host, urls[i], multi,
                     trace_enabled, headers, insecure_mode,
                     transport, resolve, &config);
      if(rc != 0) {
        fprintf(stderr, "initprobe() failed (DNS_TYPE_CNAME)\n");
        exit(1);
      }
    }
  }

...

    /* “numfds”为零意味着超时或没有文件描述符等待第一次出现时尝试超时,然后假设没有文件
描述符和无文件描述符等待意味着等待100
毫秒。 */

    if(!numfds) {
      repeats++; /* 计数重复为零的次数 */
      if(repeats > 1) {
        WAITMS(10); /* 睡眠10毫秒 */
      }
    }
    else
      repeats = 0;

    curl_multi_perform(multi, &still_running);
...

  if(successful && !test_mode) 
  {
    int i;
    printf("[%s]\n", host);
    printf("TTL: %u seconds\n", d.ttl);
    if(query_type == 0 || query_type == DNS_TYPE_A) 
    {
      for(i=0; i < d.numv4; i++) 
      {
        printf("A: %d.%d.%d.%d\n",
               d.v4addr[i]>>24,
               (d.v4addr[i]>>16) & 0xff,
               (d.v4addr[i]>>8) & 0xff,
               d.v4addr[i] & 0xff);
      }
    }
    if(query_type == 0 || query_type == DNS_TYPE_AAAA) 
    {
      for(i=0; i < d.numv6; i++) 
      {
        int j;
        printf("AAAA: ");
        for(j=0; j<16; j+=2) 
        {
          printf("%s%02x%02x", j ? ":" : "", d.v6addr[i].byte[j],
                 d.v6addr[i].byte[j+1]);
        }
        printf("\n");
      }
    }
    if(query_type == 0 || query_type == DNS_TYPE_CNAME) 
    {
      for(i=0; i < d.numcname; i++)
        printf("CNAME: %s\n", d.cname[i].alloc);
    }
    if(query_type == 0 || query_type == DNS_TYPE_TXT) 
    {
      for(i=0; i < d.numtxt; i++)
        printf("TXT: %s\n", d.txt[i].txt);
    }
  }

...
  return exit_status;
}

运行结果:


If you need the complete source code, please add the WeChat number (c17865354792)

总结

HTTPS上的DNS是传统DNS协议的加密、身份验证版本。这意味着您和DNS服务器之间传递的所有数据都是端到端完全加密的;您与网站的连接将是安全的,不易受到中间人攻击或其他类型的窃听。

Welcome to follow WeChat official account【程序猿编码

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值