-
Notifications
You must be signed in to change notification settings - Fork 23
Configuration
The configuration of dcompass mainly consists of the upstreams
definition and the script
source code, and it can be written in either YAML (recommended for readability) or JSON.
Here is an example config, and you can get a taste of what it looks like:
---
verbosity: "info"
address: 0.0.0.0:2053
script: |
pub async fn route(upstreams, inited, ctx, query) {
// A few constants are predefined:
// - query: the incoming query received
// - ctx: the query context, e.g. client IP
// - inited: the value returned by init()
// - upstreams: the upstreams API
if query.first_question?.qtype.to_str() == "AAAA" {
return blackhole(query);
}
let resp = upstreams.send_default("domestic", query).await?;
for ans in resp.answer? {
match ans.rtype.to_str() {
"A" if !inited.geoip.0.contains(ans.to_a()?.ip, "CN") => { return upstreams.send_default("secure", query).await; }
"AAAA" if !inited.geoip.0.contains(ans.to_aaaa()?.ip, "CN") => { return upstreams.send_default("secure", query).await; }
_ => continue,
}
}
Ok(resp)
}
pub async fn init() {
Ok(#{"geoip": Utils::GeoIp(GeoIp::create_default()?)})
}
upstreams:
114DNS:
udp:
addr: 114.114.114.114:53
Ali:
udp:
addr: 223.6.6.6:53
domestic:
hybrid:
- 114DNS
- Ali
cloudflare:
https:
uri: https://cloudflare-dns.com/dns-query
ratelimit: 3000
addr: 1.0.0.1
quad9:
https:
uri: https://quad9.net/dns-query
ratelimit: 3000
addr: 9.9.9.9
secure:
hybrid:
- cloudflare
- quad9
The upstreams
section is composed of several upstream
s. For example, in the above config, this is an upstream:
Ali: # name of the upstream
udp: # transport type
addr: 223.6.6.6:53 # transport details
Every upstream follows the same structure.
dcompass currently supports the following DNS transports:
- UDP (
udp
) - DNS over TLS (
tls
) - DNS over HTTPS (
https
)
And DNS over TCP will be supported in the future.
These transport types have a few common options:
-
ratelimit
(OPTIONAL): limit the number of queries the upstream will process per second. If the throughput exceeds the rate limit specified, excess queries will be dropped and returned asSERVFAIL
. This is a good measure to avoid unnecessary remote QoS if you know the remote server has a rate limit. (default: unlimited) -
max_pool_size
(OPTIONAL): specify the internal connection pool size. dcompass maintains a connection pool for each upstream to reuse connections and reduce delay. Appropriately increasing the pool size may improve throughput. However, this will also increase the time needed to replace stale connections if the network changes (e.g. network interfaces change from Wi-Fi to LTE.) (default: 46) -
timeout
(OPTIONAL): return SERVFAIL if the timeout is reached when waiting upstream to respond, specified in second. (default: 5)
In addition, each transport type has different options and details to be set.
-
addr
(REQUIRED): the address of the remote server in IP address and port. For example,1.1.1.1:53
.
-
domain
(REQUIRED): The domain of the DoH server. e.g.cloudflare-dns.com
-
addr
(REQUIRED): The address of the server. e.g.1.1.1.1:853
for Cloudflare DNS. -
reuse_timeout
(OPTIONAL): The time in millisecond to keep the underlying persistent TCP connection open for reuse (default: 60000) -
max_reuse
(OPTIONAL): The maximum number of queries to send over a single underlying TCP connection before we renew it. (default: 200) -
SNI
(OPTIONAL): whether to send Server Name Indication (SNI) when establishing TLS connection with the remote server (default: false)
-
uri
(REQUIRED): The URL of the DoH server. e.g.https://cloudflare-dns.com/dns-query
-
addr
(REQUIRED): The IP address of the server. e.g.1.1.1.1
for Cloudflare DNS. -
proxy
(OPTIONAL): The Proxy URL used to connect the upstream server. Supports HTTP and SOCKS5 proxy formats likesocks5://[user:[passwd]]@[ip:[port]]
-
SNI
(OPTIONAL): whether to send Server Name Indication (SNI) when establishing TLS connection with the remote server (default: false)
In addition to these transports, there is also a special transport type hybrid
. The hybrid
transport concurrently query the underlying upstream
s specified.
For example, the secure
upstream is a hybrid
upstream defined as:
secure:
hybrid:
- cloudflare
- quad9
It will concurrently query both upstreams named cloudflare
and quad9
. And yes! you can nest hybrid
upstreams and dcompass is smart enough to figure out recursion in your definition.
The script section allows you completely customize your workflow for every incoming query with no compromise on performance in rune scripting language. The rune language is basically like Rust with erased types.
There are few important predefined types that you may work with in the script:
-
Message
: a DNS message. It has a lot of fields and associated functions that you can use to modify (almost) everything on it. -
Upstreams
: an interface to interact with the upstreams that you defined. You can use it to send queries to your upstream. -
QueryContext
: the context of the incoming query. For example, it includes the IP address of the client. -
Objects
: a map with string as key andUtils
as value.
And also Result
and async functions are extensively used:
- Every time a function or a field returns a
Result<T>
, whereT
is any type, you should use?
operator to "unwrap" it. You shall see it in action later. - If the function you call is
async
, you should use.await
to get the result. For example, you should callasync fn foo()
byfoo().await
instead offoo()
only.
dcompass calls on functions you define to route your query. Specifically, it
- calls
pub async fn init() -> Result<Objects>
to initialize your utils. Ifinit()
doesn't exist, it skips this part and use an emptyObjects
- calls
pub async fn route(upstreams: Upstreams, inited: Objects, ctx: QueryContext, query: Message) -> Result<Message>
to route your message
The reason for initializing your utils is to avoid repeatedly loading databases or other resources. You should initialize all your utils in init
at once.
There are mainly three util types:
-
GeoIp
: A Geo IP database that helps you identify the IP's geographical origin. -
Domain
: A fast domain list that matches domain. -
IpCidr
: A fast IP CIDR list that matches IP address. In addition to that, we also have a functionpub fn blackhole(query: Message) -> Result<Message>
which generates a response that will inhibit the client from requesting again. It's often used to stop a QTYPE.
And each util has many functions that help you to create and utilize them.
-
pub fn create_default() -> Result<SealedGeoIp>
: create a GeoIp matcher from the builtin database. If the build doesn't contain a builtin database, it will error. -
pub async fn from_path(path: &str) -> Result<SealedGeoIp>
: create a GeoIp matcher from the database at the path given. -
pub fn contains(geoip: &SealedGeoIp, ip: &IpAddr, country_code: &str) -> bool
: returns whether the given IP is from the country with the given country code.
To create a GeoIP matcher, typically you go with:
pub async fn init() {
// Create other matchers...
let geoip = GeoIp::create_default()?;
// Or use specify a path
let geoip = GeoIp::from_path("/path/to/database").await?;
Ok(#{ "geoip": Utils::GeoIp(geoip) })
}
pub fn new() -> Domain
pub async fn add_file(domain: mut Domain, path: &str) -> Result<Domain>
pub fn add_qname(domain: mut Domain, qname: Dname) -> Result<Domain>
pub fn seal(domain: Domain) -> SealedDomain
pub fn contains(domain: &SealedDomain, addr: IpAddr) -> bool
Not sure how to tailor config to your specific needs? I am available for private paid help. Contact me on Telegram.
Have questions or want to chat with other users? Join dcompass Telegram group.