You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
DoT: allow fixed IP upstreams with an explicit TLS authentication name / SNI
#682
rather than requiring the network address to also be the certificate identity.
I think the RFC terminology is useful here: the second value is an Authentication Domain Name (ADN), not merely an "SNI override". SNI is one consequence of supplying the DNS name; authenticating the certificate against that name is the security-relevant part.
Current behaviour
I checked this against current main.
DoT forwarders are currently restricted to IP literals.
parseServers() removes tls:// and passes the remainder through validForwarderAddr(). That function uses net.SplitHostPort() and then requires the host component to parse with net.ParseIP(). The resulting server contains Addr and Proto: "tcp-tls" but no per-server TLS authentication name.
The dnsclient DoT path constructs a tls.Dialer and dials the configured address directly.
Go's TLS implementation has an important behaviour here. If tls.Config.ServerName is empty, tls.Dialer derives it from the hostname portion of the address passed to the dial operation. For:
9.9.9.9:853
that means the effective ServerName becomes:
9.9.9.9
Go's own source explicitly says that if no ServerName is supplied, it infers one from the hostname being connected to.
tls.Config.ServerName is then used for certificate hostname verification. Go also documents that it is sent in the TLS handshake for SNI unless it is an IP address.
So the current effective model is:
tls://9.9.9.9:853
TCP destination
└── 9.9.9.9:853
TLS reference identity
└── 9.9.9.9
Certificate requirement
└── certificate needs matching IP SAN
SNI hostname
└── not sent, because ServerName is an IP address
This is an important distinction: sdns is not skipping TLS certificate validation.
The existing behaviour performs normal TLS validation, but the only identity it can currently express for an IP-configured DoT forwarder is the IP address itself.
That works for a DoT server whose certificate contains the corresponding IP SAN.
It does not work when the service identity is a DNS name and the certificate is valid for that DNS name but not for the literal service IP.
There is currently no way for an operator to express:
Connect to this exact IP address, but require the peer to authenticate as this DNS name.
That is the configuration dimension I am proposing.
RFC 8310 explicitly models IP address and authentication name separately
I think RFC 8310 provides the strongest argument for supporting this.
RFC 8310 defines an Authentication Domain Name (ADN) as the domain-name identity used when authenticating a privacy-enabling DNS server.
Section 7.1, Full Direct Configuration, explicitly describes a DNS client being securely provisioned with:
IP address + Authentication Domain Name
for each DNS server.
The RFC says direct configuration can consist of both an IP address and an authentication domain name for each DNS server, obtained out of band. It even lists ADN + IP as a distinct static authentication configuration in its overview table.
So the standards model already separates:
DNS privacy server
│
├── IP address
│ └── where to connect
│
└── Authentication Domain Name
└── which service identity to authenticate
Section 8.1 then describes PKIX authentication using that ADN.
The client verifies the certificate chain and constructs a DNS reference identifier from the Authentication Domain Name. That identity is compared against the certificate's subjectAltName.
So this proposed separation is not an sdns-specific workaround.
It is directly aligned with the configuration model RFC 8310 describes for authenticated DNS-over-TLS.
This also avoids a bootstrap dependency
The separation is useful because:
network address
and:
authenticated service identity
answer different questions.
A fixed IP says:
Where should I connect?
The Authentication Domain Name says:
Which service must answer there?
If both values are explicitly configured:
IP = 9.9.9.9
ADN = dns.quad9.net
there is no need to resolve dns.quad9.net before establishing the DoT connection.
The hostname is not being supplied so sdns can discover the destination address.
It is being supplied so TLS can authenticate the destination reached at the already-configured address.
That maps directly onto RFC 8310's ADN + IP model.
sdns already separates connection address from TLS identity for hostname-based DoH
There is also a useful internal precedent.
Current hostname-based DoH already separates the address used for the underlying TCP connection from the hostname used as the TLS identity.
newDoHServer() resolves a configured DoH hostname once during startup and pins the resulting IP addresses into a custom DialContext. The HTTP transport therefore connects to those pinned addresses rather than performing a fresh DNS lookup for every connection.
At the same time, TLSClientConfig.ServerName remains the original hostname.
The source comments explicitly explain why:
TLS ServerName is set to the original hostname even when we dial
an IP literal, this preserves SNI and cert-chain validation.
That seems consistent with the security model already used by sdns for hostname-based DoH, while removing the bootstrap lookup because the operator supplied the destination address directly.
Existing resolver precedent
This capability also appears across a fairly broad set of independent DNS implementations.
Implementation
Fixed network endpoint
Independent TLS identity
Unbound
forward-addr: IP
An optional #name causes the TLS certificate to be authenticated against that name.
Stubby / getdns
address_data: IP
tls_auth_name is explicitly defined as the Authentication Domain Name verified against the certificate.
Knot Resolver
Explicit IP supplied to policy.TLS_FORWARD()
hostname='res.example.com' is checked against the certificate and sent as SNI.
CoreDNS
tls://9.9.9.9
Supports tls_servername dns.quad9.net and per-endpoint tls://9.9.9.9%dns.quad9.net.
dnsdist
Backend address remains an IP
subjectName / subject_name is sent as SNI and used when validating the backend certificate.
PowerDNS Recursor
Outgoing TLS configuration can match a forwarding target by IP/subnet
subject_name can independently specify the SNI/certificate identity when certificate validation is enabled.
BIND 9.20+
Forwarders can be configured as IP addresses using DoT
TLS configuration supports remote-hostname as the expected hostname in the remote certificate.
systemd-resolved
Explicit DNS server IP
address#server_name uses server_name for certificate validation and SNI; without it strict validation checks against the IP.
These implementations differ in defaults and exact syntax, but they all expose the same useful distinction: transport destination and authenticated TLS name need not be the same string.
Unbound
Unbound's forward-addr syntax is particularly close:
forward-addr: 9.9.9.9@853#dns.quad9.net
Its documentation says that when TLS is enabled, a name can be appended with # and Unbound will check the TLS authentication certificate using that name.
One difference is worth stating explicitly: current Unbound documentation says that if #name is omitted from forward-addr, any certificate name is accepted as long as the certificate matches the configured CA trust.
That default behaviour is not what I am proposing for sdns, and it differs from sdns's current IP-based identity verification.
I am only pointing to Unbound as precedent for allowing:
fixed IP + separately configured TLS authentication name
sdns could retain its existing secure behaviour when no name is supplied.
Stubby / getdns
Stubby's configuration is almost a direct representation of RFC 8310 terminology:
Its documentation says each target IP needs explicit authentication configuration and that hostname must match the server certificate and is also sent as SNI.
Conceptually:
2001:DB8::d0c
└── connection destination
res.example.com
├── certificate identity
└── SNI
CoreDNS
CoreDNS has an especially relevant example.
Its forward plugin documentation explicitly shows:
tls://9.9.9.9
tls_servername dns.quad9.net
and says 9.9.9.9 needs the TLS server name set to dns.quad9.net.
Current CoreDNS also supports per-destination TLS names:
tls://9.9.9.9%dns.quad9.net
This is essentially the same information I would like sdns to be able to represent:
dial 9.9.9.9
authenticate dns.quad9.net
dnsdist
dnsdist supports outgoing DoT to an explicit backend address:
address="[2001:DB8::1]:853"
while independently accepting:
subjectName="dot.powerdns.com"
Its documentation says subjectName is passed as SNI and used to validate the backend certificate.
PowerDNS Recursor
PowerDNS Recursor's outgoing TLS configuration can match a target using its remote IP/subnet while independently specifying subject_name.
and describes that entry as matching on IP while enabling validation using the SNI name dns.google.
For accuracy, certificate validation in PowerDNS Recursor is configurable and validate_certificate currently defaults to false, so I am not suggesting its default security behaviour is identical to sdns.
The relevant precedent is the ability to configure:
remote IP = 8.8.8.8
subject_name = dns.google
independently.
BIND 9.20+
BIND added TLS support to forwarders in BIND 9.20.0.
Its TLS configuration supports:
remote-hostname
which the BIND documentation defines as the expected hostname in the TLS certificate of the remote server.
For outgoing connections, if remote-hostname is not specified but certificate authentication is configured, BIND can instead use the remote IP address.
Again, that reflects the same distinction:
remote network address
versus
expected certificate hostname
systemd-resolved
systemd-resolved perhaps exposes the clearest equivalent syntax:
address#server_name
Its documentation says that with DNS-over-TLS enabled, when server_name is present it is used to validate the certificate and enable SNI.
Otherwise the certificate is checked against the server IP.
So its behaviour can be represented almost exactly as:
when an operator explicitly configures them that way.
Why not simply allow tls://hostname:853?
I think that would be useful too, but it solves a different problem.
If an operator configures:
tls://dns.example.com:853
then sdns needs some mechanism to determine the network address of dns.example.com before it can establish the DoT connection.
That introduces a bootstrap DNS dependency unless the hostname is resolved and pinned in a manner similar to current DoH.
By contrast:
tls://192.0.2.53:853#dns.example.com
already contains everything required:
where to connect:
192.0.2.53
what to authenticate:
dns.example.com
No A/AAAA lookup is necessary.
There are legitimate operational reasons to want exactly that:
avoid a DNS bootstrap dependency;
choose IPv4 or IPv6 explicitly;
select a specific anycast/unicast service address;
keep routing deterministic;
retain a provider's stable DNS certificate identity independently from its service IP;
use a DoT provider whose certificate contains the provider DNS identity but not an IP SAN.
This is also exactly the configuration shape RFC 8310 calls ADN + IP.
So hostname-based DoT and fixed-IP-plus-ADN DoT would be complementary features, not substitutes.
Why not require every DoT provider to include its service IP in the certificate?
Because that unnecessarily couples network location and service identity.
There are two separate assertions:
9.9.9.9
= connect here
dns.quad9.net
= require this authenticated service identity
An IP SAN is perfectly valid and current sdns behaviour should continue to support it.
But it should not have to be the only authentication model available.
RFC 8310 explicitly defines Authentication Domain Names and explicitly describes direct configuration containing both an IP address and an ADN.
Multiple existing DNS implementations expose the same distinction.
So allowing a DNS reference identity does not weaken TLS or work around PKIX.
It allows the operator to specify the PKIX identity that the service actually uses.
Why not disable certificate verification?
That would solve the wrong problem.
Desired behaviour is not:
connect to 9.9.9.9
encrypt traffic
accept whatever certificate arrives
It is:
connect to configured IP
+
verify certificate chain
+
require configured DNS identity
+
send that DNS identity as SNI
For example:
configured:
IP = 9.9.9.9
ADN = dns.quad9.net
certificate:
SAN = dns.quad9.net
result:
valid
but:
configured:
IP = 9.9.9.9
ADN = dns.quad9.net
certificate:
SAN = attacker.example
result:
TLS authentication failure
and:
configured:
IP = 9.9.9.9
ADN = dns.quad9.net
certificate:
valid CA chain
but no dns.quad9.net SAN
result:
TLS authentication failure
So this would preserve, rather than weaken, strict certificate authentication.
There should also be no retry of the same endpoint with the configured name removed or certificate verification disabled.
Current sdns does support failover across configured forwarders: if one upstream returns an error, the forwarder loop may try another configured server. That behaviour should remain unchanged.
So the intended rule is:
authentication failure for DoT endpoint
│
├── do NOT downgrade that endpoint
├── do NOT retry it without ADN validation
├── do NOT disable certificate verification
│
└── normal failover to another explicitly configured
upstream may continue as it does today
That distinction matters if an operator intentionally has a mixed upstream list containing DoT and non-DoT servers.
One possible implementation
I do not want to prescribe internal implementation details, but looking at the current code, this appears capable of being implemented without inventing a custom certificate verification mechanism.
Current server entries contain:
type server struct {
Addr string
Proto string
// DoH-only fields...
}
and dnsclient.Client already accepts:
TLSConfig *tls.Config
for tcp-tls.
Go already provides the required semantics through:
tls.Config{
ServerName: "dns.quad9.net",
}
because a DNS-valued ServerName is used for certificate hostname validation and SNI.
So one possible representation could be:
type server struct {
Addr string
Proto string
TLSServerName string
I mention this mainly because otherwise parsing two intentionally distinct TLS identities for the same address could accidentally collapse them into one server.
Global forwarders and forward zones
Current code routes both:
cfg.ForwarderServers
and:
ForwardZones[i].Servers
through parseServers().
So if the syntax were implemented at that parsing layer, it looks possible for the same DoT authentication-name feature to work consistently for both:
forwarderservers = [...]
and per-zone forwarding.
That would be useful because private/internal forwarding is another case where the network endpoint and certificate identity may deliberately differ.
Suggested validation/tests
I would expect behaviour approximately like this:
Existing tls://IP:853 configurations continue to behave exactly as they do today.
tls://IP:853 with an explicit Authentication Domain Name connects to exactly the configured IP.
The Authentication Domain Name is not resolved through DNS when a fixed IP is present.
The configured DNS name becomes tls.Config.ServerName.
Because it is a DNS name rather than an IP, it is sent as TLS SNI.
A trusted certificate containing a matching DNS subjectAltName succeeds.
A trusted certificate without the configured DNS identity fails authentication for that upstream.
A certificate-chain failure remains a TLS failure.
Authentication failure does not cause the same endpoint to be retried using IP identity.
Authentication failure does not disable certificate verification.
Existing failover to another explicitly configured upstream continues normally.
IPv4 and IPv6 fixed endpoints behave equivalently.
If configuration allows the same IP with different authentication names, those entries are not incorrectly deduplicated.
The same syntax/semantics work for both whole-server forwarders and forward-zone upstreams.
If the authentication-name syntax is malformed, configuration rejects or skips that entry consistently with existing forwarder validation behaviour.
The core invariant would be:
configured IP
controls routing
configured ADN
controls authentication
Neither silently replaces the other.
Why I think this is worthwhile
The case for it seems fairly broad:
Standards
RFC 8310 explicitly defines direct configuration as
IP address + Authentication Domain Name.
Security
Certificate verification remains enabled.
Operator explicitly states expected service identity.
Interoperability
DoT servers are not required to express their service
identity using an IP SAN.
SNI
DNS authentication name can be sent as SNI even though
TCP connects to a literal IP.
Bootstrap
No DNS lookup is needed because destination IP is already
explicitly configured.
Determinism
Operator controls exact IPv4/IPv6 destination independently
from certificate identity.
Consistency
sdns already separates pinned network address from TLS
ServerName for hostname-based DoH.
Existing practice
Unbound, Stubby/getdns, Knot Resolver, CoreDNS, dnsdist,
PowerDNS Recursor, BIND 9.20+, and systemd-resolved all
expose some form of network-address / TLS-name separation.
Compatibility
Existing tls://IP:port configurations can retain their
current behaviour unchanged.
Implementation
Go TLS already provides the necessary certificate and SNI
semantics through tls.Config.ServerName.
This therefore feels less like adding a special case for one DoT provider and more like exposing a normal part of authenticated DoT configuration that sdns currently cannot represent.
No existing IP-only configuration needs to change.
Request
Would you consider adding an optional Authentication Domain Name / TLS ServerName to DoT forwarders?
In its simplest form, the behaviour would be:
configured IP
= where sdns connects
configured authentication name
= what sdns requires the TLS peer to authenticate as
For example:
9.9.9.9:853 + dns.quad9.net
would mean:
connect to:
9.9.9.9:853
send SNI:
dns.quad9.net
validate certificate for:
dns.quad9.net
while:
tls://9.9.9.9:853
without an explicit authentication name would keep the existing IP-based behaviour.
I think that would align DoT forwarding more closely with RFC 8310's ADN + IP model, make it interoperable with a configuration pattern already exposed by several other DNS implementations, and mirror the connection-address / TLS-identity separation that sdns already uses internally for hostname-based DoH — without adding a DNS bootstrap dependency or weakening certificate validation.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Would you consider extending DoT forwarders so the network address and TLS authentication identity can be configured independently?
Conceptually:
For example, something along the lines of:
Exact syntax is not important to me.
#name,?servername=name, a second config field, or a structured representation could all express the same thing.What matters is allowing a DoT upstream to contain two independent pieces of information:
rather than requiring the network address to also be the certificate identity.
I think the RFC terminology is useful here: the second value is an Authentication Domain Name (ADN), not merely an "SNI override". SNI is one consequence of supplying the DNS name; authenticating the certificate against that name is the security-relevant part.
Current behaviour
I checked this against current
main.DoT forwarders are currently restricted to IP literals.
parseServers()removestls://and passes the remainder throughvalidForwarderAddr(). That function usesnet.SplitHostPort()and then requires the host component to parse withnet.ParseIP(). The resulting server containsAddrandProto: "tcp-tls"but no per-server TLS authentication name.Conceptually:
The
dnsclientDoT path constructs atls.Dialerand dials the configured address directly.Go's TLS implementation has an important behaviour here. If
tls.Config.ServerNameis empty,tls.Dialerderives it from the hostname portion of the address passed to the dial operation. For:that means the effective
ServerNamebecomes:Go's own source explicitly says that if no
ServerNameis supplied, it infers one from the hostname being connected to.tls.Config.ServerNameis then used for certificate hostname verification. Go also documents that it is sent in the TLS handshake for SNI unless it is an IP address.So the current effective model is:
This is an important distinction: sdns is not skipping TLS certificate validation.
The existing behaviour performs normal TLS validation, but the only identity it can currently express for an IP-configured DoT forwarder is the IP address itself.
That works for a DoT server whose certificate contains the corresponding IP SAN.
It does not work when the service identity is a DNS name and the certificate is valid for that DNS name but not for the literal service IP.
There is currently no way for an operator to express:
That is the configuration dimension I am proposing.
RFC 8310 explicitly models IP address and authentication name separately
I think RFC 8310 provides the strongest argument for supporting this.
RFC 8310 defines an Authentication Domain Name (ADN) as the domain-name identity used when authenticating a privacy-enabling DNS server.
Section 7.1, Full Direct Configuration, explicitly describes a DNS client being securely provisioned with:
for each DNS server.
The RFC says direct configuration can consist of both an IP address and an authentication domain name for each DNS server, obtained out of band. It even lists
ADN + IPas a distinct static authentication configuration in its overview table.So the standards model already separates:
Section 8.1 then describes PKIX authentication using that ADN.
The client verifies the certificate chain and constructs a DNS reference identifier from the Authentication Domain Name. That identity is compared against the certificate's
subjectAltName.So this proposed separation is not an sdns-specific workaround.
It is directly aligned with the configuration model RFC 8310 describes for authenticated DNS-over-TLS.
This also avoids a bootstrap dependency
The separation is useful because:
and:
answer different questions.
A fixed IP says:
The Authentication Domain Name says:
If both values are explicitly configured:
there is no need to resolve
dns.quad9.netbefore establishing the DoT connection.The hostname is not being supplied so sdns can discover the destination address.
It is being supplied so TLS can authenticate the destination reached at the already-configured address.
That maps directly onto RFC 8310's
ADN + IPmodel.sdns already separates connection address from TLS identity for hostname-based DoH
There is also a useful internal precedent.
Current hostname-based DoH already separates the address used for the underlying TCP connection from the hostname used as the TLS identity.
newDoHServer()resolves a configured DoH hostname once during startup and pins the resulting IP addresses into a customDialContext. The HTTP transport therefore connects to those pinned addresses rather than performing a fresh DNS lookup for every connection.At the same time,
TLSClientConfig.ServerNameremains the original hostname.The source comments explicitly explain why:
and the actual transport configuration sets:
So hostname-based DoH effectively does this:
I am not suggesting that DoH and the proposed DoT configuration are identical.
DoH obtains the IP through startup bootstrap resolution; proposed DoT configuration would have the operator provide the IP explicitly.
But sdns already implements the underlying connection-address / TLS-identity separation principle.
Proposed DoT:
That seems consistent with the security model already used by sdns for hostname-based DoH, while removing the bootstrap lookup because the operator supplied the destination address directly.
Existing resolver precedent
This capability also appears across a fairly broad set of independent DNS implementations.
These implementations differ in defaults and exact syntax, but they all expose the same useful distinction: transport destination and authenticated TLS name need not be the same string.
Unbound
Unbound's
forward-addrsyntax is particularly close:Its documentation says that when TLS is enabled, a name can be appended with
#and Unbound will check the TLS authentication certificate using that name.One difference is worth stating explicitly: current Unbound documentation says that if
#nameis omitted fromforward-addr, any certificate name is accepted as long as the certificate matches the configured CA trust.That default behaviour is not what I am proposing for sdns, and it differs from sdns's current IP-based identity verification.
I am only pointing to Unbound as precedent for allowing:
sdns could retain its existing secure behaviour when no name is supplied.
Stubby / getdns
Stubby's configuration is almost a direct representation of RFC 8310 terminology:
Its documentation defines:
The supplied example configuration contains exactly this pattern for multiple public DoT services, including Quad9:
Cloudflare:
Google:
and others including AdGuard and Comcast.
That seems like particularly strong evidence that this is a normal operational requirement rather than an unusual edge case.
Knot Resolver
Knot Resolver makes the separation explicit in its API:
Its documentation says each target IP needs explicit authentication configuration and that
hostnamemust match the server certificate and is also sent as SNI.Conceptually:
CoreDNS
CoreDNS has an especially relevant example.
Its forward plugin documentation explicitly shows:
and says
9.9.9.9needs the TLS server name set todns.quad9.net.Current CoreDNS also supports per-destination TLS names:
This is essentially the same information I would like sdns to be able to represent:
dnsdist
dnsdist supports outgoing DoT to an explicit backend address:
while independently accepting:
Its documentation says
subjectNameis passed as SNI and used to validate the backend certificate.PowerDNS Recursor
PowerDNS Recursor's outgoing TLS configuration can match a target using its remote IP/subnet while independently specifying
subject_name.Its documentation includes this example:
and describes that entry as matching on IP while enabling validation using the SNI name
dns.google.For accuracy, certificate validation in PowerDNS Recursor is configurable and
validate_certificatecurrently defaults tofalse, so I am not suggesting its default security behaviour is identical to sdns.The relevant precedent is the ability to configure:
independently.
BIND 9.20+
BIND added TLS support to
forwardersin BIND 9.20.0.Its TLS configuration supports:
which the BIND documentation defines as the expected hostname in the TLS certificate of the remote server.
For outgoing connections, if
remote-hostnameis not specified but certificate authentication is configured, BIND can instead use the remote IP address.Again, that reflects the same distinction:
systemd-resolved
systemd-resolved perhaps exposes the clearest equivalent syntax:
Its documentation says that with DNS-over-TLS enabled, when
server_nameis present it is used to validate the certificate and enable SNI.Otherwise the certificate is checked against the server IP.
So its behaviour can be represented almost exactly as:
That is essentially the behaviour I am requesting.
Possible configuration
To preserve the current
forwarderservers = []stringformat, one possible syntax would be similar to Unbound or systemd-resolved:IPv6 remains unambiguous because the address is already bracketed:
Semantics:
versus:
Critically:
The configured IP remains authoritative for routing.
The hostname is an authentication parameter, not an address-discovery mechanism.
If
#is undesirable because the string resembles a URL and#conventionally represents a fragment, another syntax could be used, for example:or a more explicit structured configuration in future:
I do not have a strong preference on syntax.
The important semantic property is:
when an operator explicitly configures them that way.
Why not simply allow
tls://hostname:853?I think that would be useful too, but it solves a different problem.
If an operator configures:
then sdns needs some mechanism to determine the network address of
dns.example.combefore it can establish the DoT connection.That introduces a bootstrap DNS dependency unless the hostname is resolved and pinned in a manner similar to current DoH.
By contrast:
already contains everything required:
No A/AAAA lookup is necessary.
There are legitimate operational reasons to want exactly that:
avoid a DNS bootstrap dependency;
choose IPv4 or IPv6 explicitly;
select a specific anycast/unicast service address;
keep routing deterministic;
retain a provider's stable DNS certificate identity independently from its service IP;
use a DoT provider whose certificate contains the provider DNS identity but not an IP SAN.
This is also exactly the configuration shape RFC 8310 calls
ADN + IP.So hostname-based DoT and fixed-IP-plus-ADN DoT would be complementary features, not substitutes.
Why not require every DoT provider to include its service IP in the certificate?
Because that unnecessarily couples network location and service identity.
There are two separate assertions:
An IP SAN is perfectly valid and current sdns behaviour should continue to support it.
But it should not have to be the only authentication model available.
RFC 8310 explicitly defines Authentication Domain Names and explicitly describes direct configuration containing both an IP address and an ADN.
Multiple existing DNS implementations expose the same distinction.
So allowing a DNS reference identity does not weaken TLS or work around PKIX.
It allows the operator to specify the PKIX identity that the service actually uses.
Why not disable certificate verification?
That would solve the wrong problem.
Desired behaviour is not:
It is:
For example:
but:
and:
So this would preserve, rather than weaken, strict certificate authentication.
There should also be no retry of the same endpoint with the configured name removed or certificate verification disabled.
Current sdns does support failover across configured forwarders: if one upstream returns an error, the forwarder loop may try another configured server. That behaviour should remain unchanged.
So the intended rule is:
That distinction matters if an operator intentionally has a mixed upstream list containing DoT and non-DoT servers.
One possible implementation
I do not want to prescribe internal implementation details, but looking at the current code, this appears capable of being implemented without inventing a custom certificate verification mechanism.
Current
serverentries contain:and
dnsclient.Clientalready accepts:for
tcp-tls.Go already provides the required semantics through:
because a DNS-valued
ServerNameis used for certificate hostname validation and SNI.So one possible representation could be:
Parsing:
Then the DoT client for that server could use a TLS config derived for that endpoint:
while still calling:
The result would naturally be:
No custom SAN parsing or certificate-verification code should be necessary if standard Go TLS verification remains enabled.
That is only one possible implementation; there may be a cleaner representation that fits the project's configuration architecture better.
Backward compatibility
I think this can be fully opt-in.
Existing configuration:
would retain existing semantics:
New configuration:
would explicitly request:
So no existing configuration needs to change meaning.
That seems preferable to changing DoT globally to trust arbitrary certificate names or disabling hostname verification for IP upstreams.
Duplicate handling may need to include the authentication identity
Current
parseServers()deduplicates non-DoH servers based on protocol plus canonicalised address.If authentication identity becomes per-upstream configuration, then these potentially represent distinct endpoints:
Same TCP destination:
but different required TLS identities:
Depending on the chosen internal representation, the deduplication key would therefore likely need to account for authentication identity as well.
Conceptually:
rather than only:
I mention this mainly because otherwise parsing two intentionally distinct TLS identities for the same address could accidentally collapse them into one server.
Global forwarders and forward zones
Current code routes both:
and:
through
parseServers().So if the syntax were implemented at that parsing layer, it looks possible for the same DoT authentication-name feature to work consistently for both:
and per-zone forwarding.
That would be useful because private/internal forwarding is another case where the network endpoint and certificate identity may deliberately differ.
Suggested validation/tests
I would expect behaviour approximately like this:
Existing
tls://IP:853configurations continue to behave exactly as they do today.tls://IP:853with an explicit Authentication Domain Name connects to exactly the configured IP.The Authentication Domain Name is not resolved through DNS when a fixed IP is present.
The configured DNS name becomes
tls.Config.ServerName.Because it is a DNS name rather than an IP, it is sent as TLS SNI.
A trusted certificate containing a matching DNS
subjectAltNamesucceeds.A trusted certificate without the configured DNS identity fails authentication for that upstream.
A certificate-chain failure remains a TLS failure.
Authentication failure does not cause the same endpoint to be retried using IP identity.
Authentication failure does not disable certificate verification.
Existing failover to another explicitly configured upstream continues normally.
IPv4 and IPv6 fixed endpoints behave equivalently.
If configuration allows the same IP with different authentication names, those entries are not incorrectly deduplicated.
The same syntax/semantics work for both whole-server forwarders and forward-zone upstreams.
If the authentication-name syntax is malformed, configuration rejects or skips that entry consistently with existing forwarder validation behaviour.
The core invariant would be:
Neither silently replaces the other.
Why I think this is worthwhile
The case for it seems fairly broad:
This therefore feels less like adding a special case for one DoT provider and more like exposing a normal part of authenticated DoT configuration that sdns currently cannot represent.
Proposed semantics in one diagram
No lookup of
dns.quad9.netis necessary.No certificate validation is bypassed.
No existing IP-only configuration needs to change.
Request
Would you consider adding an optional Authentication Domain Name / TLS ServerName to DoT forwarders?
In its simplest form, the behaviour would be:
For example:
would mean:
while:
without an explicit authentication name would keep the existing IP-based behaviour.
I think that would align DoT forwarding more closely with RFC 8310's
ADN + IPmodel, make it interoperable with a configuration pattern already exposed by several other DNS implementations, and mirror the connection-address / TLS-identity separation that sdns already uses internally for hostname-based DoH — without adding a DNS bootstrap dependency or weakening certificate validation.All reactions