Configuration

edit

Every setting is in the DnsProxy section of the configuration. See Change the settings for the ways to set them; the examples use command-line arguments.

Upstreams

Upstreams lists the resolvers the proxy forwards to. Each entry has a Url, a Name (shown on the diagnostics page) and a Priority:

Meziantou.DnsProxy --DnsProxy:Upstreams:0:Name=Primary --DnsProxy:Upstreams:0:Url=https://dns.example.net/dns-query --DnsProxy:Upstreams:1:Name=Fallback --DnsProxy:Upstreams:1:Url=tls://dns.example.org --DnsProxy:Upstreams:1:Priority=1

The scheme of the URL selects the transport:

URLTransport
https://host/path, h3://host/pathDNS over HTTPS, to that URL
quic://host, quic://host:portDNS over QUIC, port 853 by default
tls://host, tls://host:portDNS over TLS, port 853 by default
tcp://host, udp://hostPlain TCP or UDP, port 53 by default
  • The proxy tries the upstreams in Priority order, lowest first, for every query. A transport failure, a timeout (5 seconds), ServerFailure or Refused moves on to the next one. NoError and NameError are answers: they are returned at once. When every upstream fails, the client gets the last ServerFailure or Refused, or ServerFailure.
  • There is no health tracking: an upstream that is down is tried again for each query, before the next one.
  • Where QUIC is not supported, a quic://host upstream is replaced by DNS over HTTPS to https://host/dns-query, with a warning in the log.
  • A URL with another scheme, or without a scheme, stops the proxy at startup.

By default, the proxy uses six public resolvers, over DNS over HTTPS and DNS over QUIC. The appsettings.json of the project lists them, in Upstreams:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "localhost",
  "DnsProxy": {
    "DnsPort": 5053,
    "HttpPort": 5080,
    "BindAddresses": [ "127.0.0.1", "::1" ],
    "AllowedClientNetworks": [],
    "DnsOverHttpsPort": 0,
    "DnsOverHttpsPath": "/dns-query",
    "DnsOverTlsPort": 0,
    "DnsOverQuicPort": 0,
    "CertificatePath": "",
    "CertificatePassword": "",
    "DiagnosticsHistoryCapacity": 10000,
    "FilterRefreshInterval": "00:30:00",
    "FilterDownloadTimeout": "00:02:00",
    "MaxFilterListSizeInBytes": 67108864,
    "BlockListCacheFolderPath": "",
    "PositiveCacheDuration": "00:05:00",
    "NegativeCacheDuration": "00:05:00",
    "MaximumCacheDuration": "01:00:00",
    "MaxCacheEntries": 10000,
    "MaxDnsQueriesPerClientPerMinute": 600,
    "DnssecValidationMode": "None",
    "BootstrapDnsServers": [
      "9.9.9.9",
      "149.112.112.112",
      "1.1.1.1",
      "1.0.0.1",
      "2620:fe::fe",
      "2620:fe::9",
      "2606:4700:4700::1111",
      "2606:4700:4700::1001"
    ],
    "Upstreams": [
      {
        "Name": "Cloudflare H3",
        "Url": "h3://cloudflare-dns.com/dns-query",
        "Priority": 0
      },
      {
        "Name": "NextDNS DoQ",
        "Url": "quic://dns.nextdns.io",
        "Priority": 1
      },
      {
        "Name": "Quad9 DoQ",
        "Url": "quic://dns.quad9.net",
        "Priority": 2
      },
      {
        "Name": "Cloudflare DoH",
        "Url": "https://cloudflare-dns.com/dns-query",
        "Priority": 3
      },
      {
        "Name": "NextDNS DoH",
        "Url": "https://dns.nextdns.io",
        "Priority": 4
      },
      {
        "Name": "Quad9 DoH",
        "Url": "https://dns.quad9.net/dns-query",
        "Priority": 5
      }
    ],
    "Filters": [
      {
        "Url": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_1.txt",
        "Format": "AdBlock"
      },
      {
        "Url": "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts",
        "Format": "Hosts"
      }
    ],
    "CustomRecords": [
      {
        "Domain": "localhost",
        "Type": "A",
        "Value": "127.0.0.1"
      },
      {
        "Domain": "localhost",
        "Type": "AAAA",
        "Value": "::1"
      }
    ]
  }
}

Bootstrap servers

An upstream URL has a host name, which must be resolved without the proxy itself. BootstrapDnsServers lists the IP addresses of the DNS servers that resolve it, over UDP:

  • For DNS over HTTPS upstreams, the host name is resolved for each new connection, and the result is cached for five minutes.
  • For TLS, QUIC, TCP and UDP upstreams, the host name is resolved once, at startup; the proxy does not start when it cannot be resolved.
  • Entries that are not IP addresses are ignored. Without any valid entry, the system resolver is used.

Filter lists

Filters lists the block lists. Each entry has a Url and a Format (AutoDetect, Hosts, DomainsOnly or AdBlock; see Formats):

Meziantou.DnsProxy --DnsProxy:Filters:0:Url=https://lists.example.net/ads.txt --DnsProxy:Filters:0:Format=AdBlock
SettingDefaultUse
FilterRefreshInterval30 minutesHow often the lists are downloaded again.
FilterDownloadTimeout2 minutesThe longest download of one list.
MaxFilterListSizeInBytes64 MBThe largest list. A larger one is rejected.
BlockListCacheFolderPathmeziantou/dnsproxy/block-lists in the local application data folderWhere the downloaded lists are kept.
  • At startup, the proxy loads the lists from the cache folder, so filtering works at once, then downloads them.
  • When a download fails (error, timeout, size limit), the proxy uses the cached copy of that list and logs a warning. A list that has never been downloaded has no rules until a download succeeds.
  • The lines that are not valid rules are skipped, and their number is logged with a few examples. See See the lines that were skipped.
  • The URL must be http or https: local files are not supported.
  • A blocked query gets NXDOMAIN. An exception rule (@@) lets the query go to the cache and the upstreams.
  • $client rules match by client address only: the proxy does not know the names of the clients, nor any tags, so $client rules with a name and $ctag rules never match. See Client rules.
  • A $dnsrewrite rule is answered with a TTL of 60 seconds, for the types A, AAAA, CNAME, PTR, NS, TXT, MX, SRV and CAA. The proxy only answers queries of the type of the rewrite: a CNAME rewrite answers CNAME queries, and an A query for the same name gets an empty NoError answer. A rewrite of a type outside this list, such as SVCB, is answered as a block.

By default, the proxy uses two public block lists, one in Adblock format and one hosts file.

Custom records

CustomRecords defines your own records, for the names of your local network. Each entry has a Domain, a Type (A by default) and a Value, or several Values:

Meziantou.DnsProxy --DnsProxy:CustomRecords:0:Domain=nas.home.arpa --DnsProxy:CustomRecords:0:Value=192.168.1.11 --DnsProxy:CustomRecords:1:Domain=nas.home.arpa --DnsProxy:CustomRecords:1:Type=AAAA --DnsProxy:CustomRecords:1:Value=fd00::11
TypeValue
AAn IPv4 address: 192.168.1.11
AAAAAn IPv6 address: fd00::11
CNAME, PTR, NSA domain name
TXTThe text
MXThe preference and the host: 10 mail.home.arpa
SRVThe priority, the weight, the port and the target: 10 5 443 service.home.arpa
CAAThe flags, the tag and the value: 0 issue ca.example.net
  • Custom records are answered first, before the filter lists, the cache and the upstreams, with a TTL of 60 seconds. Block lists and the pause of the filtering do not affect them.
  • The name must match exactly, without case: there are no wildcards, and subdomains are not included.
  • A query for a type the name has no custom record of goes on to the filter lists and the upstreams. A CNAME custom record therefore does not answer A queries.
  • An entry with an unknown type or an invalid value is skipped with a warning.

Cache

SettingDefaultUse
PositiveCacheDuration5 minutesThe longest time an answer is cached.
NegativeCacheDuration5 minutesThe longest time an NXDOMAIN or empty answer is cached.
MaximumCacheDuration1 hourThe longest time any answer is cached.
MaxCacheEntries10000The number of cached answers. 0 disables the cache.
  • An answer is cached for the lowest TTL of its records, capped by these durations. A negative answer is cached for the TTL of its SOA record, capped the same way, or for NegativeCacheDuration without SOA.
  • ServerFailure, Refused and the other error codes are not cached, nor are blocked, rewritten and custom answers.
  • A cached answer is served with its remaining lifetime as the TTL of its records.
  • When the cache is full, the expired answers are removed first, then the oldest ones.

Rate limit

MaxDnsQueriesPerClientPerMinute (600 by default) limits the queries of each client IP address, in fixed one-minute windows. The queries over the limit get Refused. 0 disables the limit.

DNSSEC

The proxy forwards the DNSSEC OK flag of the client to the upstream, and returns the DNSSEC records (RRSIG, NSEC, NSEC3, DS, DNSKEY) to the client, so a validating client can check the answers itself. It never sets the AD flag, since it does not tell the client that it validated an answer.

DnssecValidationMode (None by default) is passed to the upstream clients. With Local, they validate each answer locally, but the proxy does not use the result: a Bogus answer is still returned to the client. Local also sets the CD flag on the upstream queries, which asks the upstream resolvers not to validate. Keep the default None unless you need the validation work for another reason.