Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Possibility to utilize the SNI as an options in Routers #268

Open
cbuijs opened this issue Dec 6, 2022 · 18 comments
Open

Possibility to utilize the SNI as an options in Routers #268

cbuijs opened this issue Dec 6, 2022 · 18 comments

Comments

@cbuijs
Copy link
Contributor

cbuijs commented Dec 6, 2022

Where we now have the doh-path feature to be utilized in routers/routes rules, which was introduced in #234, it would be nice if we could use the SNI as well (hostname passed in the request), for both all TLS and HTTPS based requests.

So if a client does a request using https://dns.domain.tld/dns-query, there will the following options in routes availble

When using tls://another.dns.domain.tld, doh-path will be empty and sni would contain another.dns.domain.tld.

Maybe we need another option/variable called dns-type, based on what kind of query, it will return do53, doh, dot, doq, etc ... ?

I use NGINX as front-end now to "steer" traffic based on the hostname (DoT) or path (DoH) and would be nice to simplify the chain. For example:

tls://dns-unfiltered.domain.com - No Filtering
tls://dns-filtered.domain.com - Filtering
tls://dns-minimal.comain.com - A bit of filtering
etc...

@folbricht
Copy link
Owner

This could be done in a similar way to #234 And we could implement this for DoT as well since we'd be able to get the SNI name there too. With DoH it's a little more interesting since we could have 2 names, one used for SNI in the TLS handshake, and the other in the request URL. They typically are the same, but don't have to be. I'm leaning towards using the name in the TLS handshake to be consistent with DoT.

@cbuijs
Copy link
Contributor Author

cbuijs commented Dec 7, 2022

I agree!

@cbuijs
Copy link
Contributor Author

cbuijs commented Dec 13, 2022

SHould I create another ticket for dns-type, basic another label to test against to see on which listener the query came in on. Probably call it dns-listener or something beter.

@folbricht
Copy link
Owner

I can add that at the same time, basically including the listener-ID and allow routing based on it.

@folbricht
Copy link
Owner

Thinking a bit more about this. If we have the listenerID available for routing, is the TLS name still needed? Wouldn't it be mostly redundant? The listenerID is much more flexible since it allows routing for plain UDP/TCP as well.

@cbuijs
Copy link
Contributor Author

cbuijs commented Dec 15, 2022

We need both, I have one listener having multiple certs/domains attached to it for example.

I could have one listener/address for multiple domain-names/SNI's and then determine with routing what to do per SNI.

The listenerID is great for listeners that don't support SNI like UDP/TCP (and perhaps DoQ and DOH3 as well).

@cbuijs
Copy link
Contributor Author

cbuijs commented Dec 15, 2022

If it helps as an example, this is how I do it in DNSDIST for the moment, front-ending RouteDNS and using different listeners to cope with it:

-- Set Minimal Pool
addAction(AndRule({DSTPortRule(443), HTTPPathRule("/minimal")}), PoolAction("minimal"))
addAction(AndRule({DSTPortRule(853), SNIRule("dns-minimal.example.com")}), PoolAction("minimal"))
-- Set Unfilter Pool
addAction(AndRule({DSTPortRule(443), HTTPPathRule("/unfiltered")}), PoolAction("unfiltered"))
addAction(AndRule({DSTPortRule(853), SNIRule("dns-unfiltered.example.com")}), PoolAction("unfiltered"))
-- Set Filter Pool
addAction(DSTPortRule(53), PoolAction("filtered"))
addAction(AndRule({DSTPortRule(443), HTTPPathRule("/dns-query")}), PoolAction("filtered"))
addAction(AndRule({DSTPortRule(853), OrRule({SNIRule("dns.example.com"), SNIRule("dns-filtered.example.com")})}), PoolAction("filtered"))

In above i use the listening port (listenerID equivalent) to determine which listener and combine it either with the doh-path or the sni to set the pool of servers to use (different listeners/IP on localhost with RouteDNS). If you don't know DNSDIST, sorry for the noise :-).

Above would be nice and snug in routers and ditch DNSDIST if possible and cleaning up the config as a whole :-).

Above would be used by DOT/DOH client as following
Full-Filtering: https://dns.example.com/dns-query or tls://dns.example.com
Minimal-Filtering: https://dns.example.com/minimal or tls://dns-minimal.example com
No-Filtering: https://dns.example.com/unfiltered or tls://dns-unfiltered.example.com

So my (single) certificate includes all domain-names used above.

In routedns routers it would look something like this (I guesss):

[routers.select-filter]
routes = [
  {listenerID = "do53", resolver = "full"},
  {listenerID = "doh", dohpath = "^/dnsquery$", resolver = "full"},
  {listenerID = "dot", sni = "dns.example.com", resolver = "full"},
  {listenerID = "doh", doh-path = "^/minimal$', resolver = "minimal"},
  {listenerID = "dot", sni = "dns-minimal.example.com", resolver = "minimal"},
  {listenerID = "doh", doh-path = "^/unfiltered$', resolver = "unfilter"},
  {listenerID = "dot", sni = "dns-unfiltered.example.com", resolver = "unfilter"},
  {resolver = "drop"}
]

@folbricht
Copy link
Owner

There's now a first draft implementation on the issue-268 branch. It adds listener and servername to the route options (docs have been updated too). Not properly tested yet.

I also added the ability to specify servername in the client TLS options so it's easier to work with certs where the SAN doesn't match the endpoint address.

@cbuijs
Copy link
Contributor Author

cbuijs commented Dec 27, 2022

Cannot build:

# github.com/folbricht/routedns
../../dnslistener.go:53:1: syntax error: unexpected <<, expecting }
../../dnslistener.go:54:41: syntax error: unexpected ) at end of statement
../../dnslistener.go:59:1: syntax error: unexpected ==, expecting }
../../dnslistener.go:62:3: syntax error: non-declaration statement outside function body
../../dnslistener.go:74:38: syntax error: unexpected ), expecting name
../../dnslistener.go:77:3: syntax error: non-declaration statement outside function body

@folbricht
Copy link
Owner

The << in the error suggests you may have some merge conflicts/local modifications in your working copy. The PR build seems to have worked and I don't see any issues here either.

@cbuijs
Copy link
Contributor Author

cbuijs commented Dec 27, 2022

Works using the PR indeed. There seems to be a conflict in the SVCB record-type and dohpath when using it in a static record (works just fine in/from the master version).

Error at runtime: bad SVCB key: "dohpath=/dns-query{?dns}" at line: 1:136

The static in question:

[groups.resolver-arpa]
type = "static-responder"
rcode = 0
answer = [
        '. 3600 IN SVCB 1 dns.example.com. port=443 alpn=h2 ipv4hint=1.2.3.4 ipv6hint=2001:db8::1234 dohpath=/dns-query{?dns}',
        '. 3600 IN SVCB 1 dns.example.com. port=853 alpn=dot ipv4hint=1.2.3.4 ipv6hint=2001:db8::1234',
]
extra = [
        'dns.example.com. 3600 IN A 1.2.3.4',
        'dns.example.com. 3600 IN AAAA 2001:db8::1234'
]

Testing now, will give an update later today.

@cbuijs
Copy link
Contributor Author

cbuijs commented Dec 27, 2022

Seems to work, did a quick run, need to test longer/better but at first sight the routes with servername and listener works. Will let it run for a while and see what pops up.

Maybe: when a route is hit, we have a clearer way of displaying why the route was selected?

@cbuijs
Copy link
Contributor Author

cbuijs commented Dec 28, 2022

So far so good, works as expected.

I also solved the bad SVCB key: "dohpath=/dns-query{?dns}" at line: 1:136 by doing an update/tidy of the modules before the build with:

go get -u
go mod tidy

I think the mentioned key needs a newer version of the MiekG DNS Module but I am not sure. I always update the modules for my builds, but didn't for the PR version.

@folbricht
Copy link
Owner

Good idea, I updated all of the deps. Backported to this branch as well. You may see conflicts in go.mod/go.sum if you pull the latest from the branch

@cbuijs
Copy link
Contributor Author

cbuijs commented Jan 7, 2023

Coolio, running this now for more then 4 days without any problems!

@cbuijs cbuijs closed this as completed Jan 7, 2023
@cbuijs
Copy link
Contributor Author

cbuijs commented Apr 12, 2023

We might need to revisit this, SNI/Servername works with DOH (including QUIC transport) and DOT, but NOT for DOQ.

@cbuijs cbuijs reopened this Apr 12, 2023
@cbuijs
Copy link
Contributor Author

cbuijs commented Aug 22, 2023

Seems to work as expected now.

@cbuijs cbuijs closed this as completed Aug 22, 2023
@cbuijs cbuijs reopened this Nov 6, 2023
@cbuijs
Copy link
Contributor Author

cbuijs commented Nov 6, 2023

Seems that servername when used in a routes rule, does not work for a dtls listener. It does work for doq and dot though. Servername is not populated when a query comes through a dtls listener.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants