idoq 0.1.2

DNS over QUIC (DoQ) client / DNS over QUIC (DoQ) 客户端
Documentation

English | 中文


idoq : DNS over QUIC Client for Rust

Based on idns. See idns for DnsRace, Parse trait, caching, and more.

Table of Contents

Features

  • RFC 9250 compliant DoQ implementation
  • Built-in DoQ server list (AdGuard, ControlD, Alibaba DNS)
  • Async/await with Tokio
  • TLS 1.3 over QUIC
  • A, AAAA, MX, TXT, NS, CNAME, PTR, SRV record types
  • Zero-copy DNS message parsing
  • Lazy-initialized TLS config
  • Connection reuse with auto-reconnect

Installation

[dependencies]
idoq = "0.1"

Usage

Basic query:

use idoq::{Doq, DOQ_LI, QType};
use idns::Query;

#[tokio::main]
async fn main() {
  let client = Doq::new(DOQ_LI[0].clone());

  if let Ok(Some(answers)) = client.answer_li(QType::A, "example.com").await {
    for a in answers {
      println!("{} TTL={}", a.val, a.ttl);
    }
  }
}

Custom server:

use idoq::{Doq, host_ip, QType};
use idns::Query;

#[tokio::main]
async fn main() {
  let client = Doq::new(host_ip("dns.alidns.com", 223, 5, 5, 5));

  if let Ok(Some(answers)) = client.answer_li(QType::AAAA, "google.com").await {
    for a in answers {
      println!("{}", a.val);
    }
  }
}

MX records with Parse trait:

use idoq::{Doq, host_ip};
use idns::{Mx, Query};

#[tokio::main]
async fn main() {
  let client = Doq::new(host_ip("dns.alidns.com", 223, 5, 5, 5));

  if let Ok(Some(mx_list)) = Query::query::<Mx>(&client, "gmail.com").await {
    for mx in mx_list {
      println!("{} {} TTL={}", mx.priority, mx.server, mx.ttl);
    }
  }
}

Race multiple servers:

use idoq::{DOQ_LI, doq_li, QType};
use idns::{DnsRace, Query};

#[tokio::main]
async fn main() {
  let race = DnsRace::new(doq_li(DOQ_LI));

  if let Ok(Some(answers)) = race.answer_li(QType::A, "github.com").await {
    for a in answers {
      println!("{}", a.val);
    }
  }
}

API Reference

Types

QType

DNS query types (re-exported from idns):

Type Value Description
A 1 IPv4 address
NS 2 Name server
CNAME 5 Canonical name
PTR 12 Pointer record
MX 15 Mail exchange
TXT 16 Text record
AAAA 28 IPv6 address
SRV 33 Service record

Structs

Doq

DoQ client with connection reuse. Implements idns::Query trait.

pub struct Doq {
  pub server: HostIp,
}

Methods:

  • new(server: HostIp) -> Self
  • query(&self, domain: &str, qtype: QType) -> Result<Option<Vec<Answer>>>

HostIp

Server configuration.

pub struct HostIp {
  pub host: SmolStr,  // TLS SNI hostname
  pub ip: IpAddr,
}

Functions

host_ip

pub const fn host_ip(host: &'static str, a: u8, b: u8, c: u8, d: u8) -> HostIp

doq_li

pub fn doq_li(li: &[HostIp]) -> Vec<Doq>

Constants

DOQ_LI

Pre-configured DoQ servers:

Server IP
AdGuard DNS 94.140.14.140, 94.140.14.141
ControlD 76.76.2.11
Alibaba DNS 223.5.5.5, 223.6.6.6

site module

  • site::ADGUARD - "unfiltered.adguard-dns.com"
  • site::CONTROLD - "p0.freedns.controld.com"
  • site::ALIDNS - "dns.alidns.com"

Architecture

graph TD
    A[Client] --> B[Doq.query]
    B --> C[Doq.conn]
    C --> D{Alive?}
    D -->|Yes| E[Reuse]
    D -->|No| F[Doq.dial]
    F --> G[QUIC Endpoint]
    G --> H[TLS Handshake]
    H --> I[Connection]
    E --> J[Doq.send]
    I --> J
    J --> K[parser.build]
    K --> L[Send]
    L --> M[Receive]
    M --> N[parser.parse]
    N --> O[Answers]

Query Flow

  1. Doq.query() - Entry point
  2. Doq.conn() - Double-checked locking for connection reuse
  3. Doq.dial() - QUIC endpoint with TLS 1.3, ALPN "doq"
  4. Doq.send() - Bidirectional stream, 2-byte length prefix
  5. parser.build() - DNS message (ID=0 per RFC 9250) with EDNS
  6. parser.parse() - Extract answer records

Implementation Details

  • DNS message ID = 0 (RFC 9250)
  • 2-byte length prefix for framing
  • EDNS OPT with 4096 byte payload
  • LazyLock for ClientConfig
  • RwLock<Option<Connection>> for caching
  • Auto-reconnect on error
  • 7s timeout

Tech Stack

Component Library
QUIC quinn
TLS rustls + ring
Async tokio
Buffer bytes
Error thiserror

Directory Structure

idoq/
├── src/
│   ├── lib.rs      # API, HostIp, DOQ_LI
│   ├── doq.rs      # Doq client
│   ├── parser.rs   # DNS build/parse
│   └── error.rs    # Error types
├── tests/
│   └── main.rs
└── Cargo.toml

History

DNS over QUIC (DoQ) was standardized in RFC 9250 (May 2022), following DNS over HTTPS (DoH, RFC 8484, 2018) and DNS over TLS (DoT, RFC 7858, 2016).

QUIC, developed by Google in 2012, became RFC 9000 in 2021. It provides TLS 1.3 at transport layer with 0-RTT handshakes.

DoQ combines encrypted DNS privacy with QUIC performance: multiplexed streams prevent head-of-line blocking, connection migration handles network changes. Unlike DoH over HTTP/2 or HTTP/3, DoQ runs directly on QUIC with less overhead.

AdGuard deployed public DoQ servers in 2020, followed by Alibaba DNS and ControlD. Adoption grows as QUIC becomes the foundation for HTTP/3.


About

This project is an open-source component of js0.site ⋅ Refactoring the Internet Plan.

We are redefining the development paradigm of the Internet in a componentized way. Welcome to follow us:


idoq : Rust DNS over QUIC 客户端

基于 idnsDnsRaceParse trait、缓存等更多功能请查看 idns。

目录

特性

  • 符合 RFC 9250 的 DoQ 实现
  • 内置 DoQ 服务器列表 (AdGuard、ControlD、阿里 DNS)
  • 基于 Tokio 异步
  • TLS 1.3 + QUIC
  • 支持 A、AAAA、MX、TXT、NS、CNAME、PTR、SRV 记录
  • 零拷贝 DNS 消息解析
  • 延迟初始化 TLS 配置
  • 连接复用,自动重连

安装

[dependencies]
idoq = "0.1"

使用

基本查询:

use idoq::{Doq, DOQ_LI, QType};
use idns::Query;

#[tokio::main]
async fn main() {
  let client = Doq::new(DOQ_LI[0].clone());

  if let Ok(Some(answers)) = client.answer_li(QType::A, "example.com").await {
    for a in answers {
      println!("{} TTL={}", a.val, a.ttl);
    }
  }
}

自定义服务器:

use idoq::{Doq, host_ip, QType};
use idns::Query;

#[tokio::main]
async fn main() {
  let client = Doq::new(host_ip("dns.alidns.com", 223, 5, 5, 5));

  if let Ok(Some(answers)) = client.answer_li(QType::AAAA, "google.com").await {
    for a in answers {
      println!("{}", a.val);
    }
  }
}

使用 Parse trait 查询 MX 记录:

use idoq::{Doq, host_ip};
use idns::{Mx, Query};

#[tokio::main]
async fn main() {
  let client = Doq::new(host_ip("dns.alidns.com", 223, 5, 5, 5));

  if let Ok(Some(mx_list)) = Query::query::<Mx>(&client, "gmail.com").await {
    for mx in mx_list {
      println!("{} {} TTL={}", mx.priority, mx.server, mx.ttl);
    }
  }
}

竞速查询多个服务器:

use idoq::{DOQ_LI, doq_li, QType};
use idns::{DnsRace, Query};

#[tokio::main]
async fn main() {
  let race = DnsRace::new(doq_li(DOQ_LI));

  if let Ok(Some(answers)) = race.answer_li(QType::A, "github.com").await {
    for a in answers {
      println!("{}", a.val);
    }
  }
}

API 参考

类型

QType

DNS 查询类型 (从 idns 重导出):

类型 说明
A 1 IPv4 地址
NS 2 域名服务器
CNAME 5 别名
PTR 12 指针记录
MX 15 邮件交换
TXT 16 文本记录
AAAA 28 IPv6 地址
SRV 33 服务记录

结构体

Doq

DoQ 客户端,支持连接复用。实现 idns::Query trait。

pub struct Doq {
  pub server: HostIp,
}

方法:

  • new(server: HostIp) -> Self
  • query(&self, domain: &str, qtype: QType) -> Result<Option<Vec<Answer>>>

HostIp

服务器配置。

pub struct HostIp {
  pub host: SmolStr,  // TLS SNI 主机名
  pub ip: IpAddr,
}

函数

host_ip

pub const fn host_ip(host: &'static str, a: u8, b: u8, c: u8, d: u8) -> HostIp

doq_li

pub fn doq_li(li: &[HostIp]) -> Vec<Doq>

常量

DOQ_LI

预配置 DoQ 服务器:

服务器 IP
AdGuard DNS 94.140.14.140, 94.140.14.141
ControlD 76.76.2.11
阿里 DNS 223.5.5.5, 223.6.6.6

site 模块

  • site::ADGUARD - "unfiltered.adguard-dns.com"
  • site::CONTROLD - "p0.freedns.controld.com"
  • site::ALIDNS - "dns.alidns.com"

架构

graph TD
    A[客户端] --> B[Doq.query]
    B --> C[Doq.conn]
    C --> D{存活?}
    D -->|是| E[复用]
    D -->|否| F[Doq.dial]
    F --> G[QUIC Endpoint]
    G --> H[TLS 握手]
    H --> I[连接]
    E --> J[Doq.send]
    I --> J
    J --> K[parser.build]
    K --> L[发送]
    L --> M[接收]
    M --> N[parser.parse]
    N --> O[应答]

查询流程

  1. Doq.query() - 入口
  2. Doq.conn() - 双重检查锁定,复用连接
  3. Doq.dial() - QUIC 端点,TLS 1.3,ALPN "doq"
  4. Doq.send() - 双向流,2 字节长度前缀
  5. parser.build() - DNS 消息 (ID=0,RFC 9250) + EDNS
  6. parser.parse() - 提取应答记录

实现细节

  • DNS 消息 ID = 0 (RFC 9250)
  • 2 字节长度前缀分帧
  • EDNS OPT 4096 字节负载
  • LazyLock 延迟初始化 ClientConfig
  • RwLock<Option<Connection>> 缓存连接
  • 错误时自动重连
  • 7 秒超时

技术栈

组件
QUIC quinn
TLS rustls + ring
异步 tokio
缓冲 bytes
错误 thiserror

目录结构

idoq/
├── src/
│   ├── lib.rs      # API、HostIp、DOQ_LI
│   ├── doq.rs      # Doq 客户端
│   ├── parser.rs   # DNS 构建/解析
│   └── error.rs    # 错误类型
├── tests/
│   └── main.rs
└── Cargo.toml

历史

DNS over QUIC (DoQ) 于 2022 年 5 月在 RFC 9250 中标准化,继 DNS over HTTPS (DoH, RFC 8484, 2018) 和 DNS over TLS (DoT, RFC 7858, 2016) 之后。

QUIC 由 Google 于 2012 年开发,2021 年成为 RFC 9000。它在传输层提供 TLS 1.3 加密,支持 0-RTT 握手。

DoQ 结合加密 DNS 的隐私优势与 QUIC 的性能优势:多路复用避免队头阻塞,连接迁移处理网络切换。与运行在 HTTP/2 或 HTTP/3 上的 DoH 不同,DoQ 直接运行在 QUIC 上,开销更低。

AdGuard 于 2020 年率先部署公共 DoQ 服务器,随后阿里 DNS、ControlD 跟进。随着 QUIC 成为 HTTP/3 基础,DoQ 采用率持续增长。


关于

本项目为 js0.site ⋅ 重构互联网计划 的开源组件。

我们正在以组件化的方式重新定义互联网的开发范式,欢迎关注: