folbricht / routedns

DNS stub resolver, proxy and router with support for DoT, DoH, DoQ, and DTLS
BSD 3-Clause "New" or "Revised" License
479 stars 63 forks source link

Support templates in static-responder #367

Closed folbricht closed 3 months ago

folbricht commented 8 months ago

Adds support for passing a regex that's applied to the question string and can then be used to customize the answers from a static-responder like so

[groups.static]
type   = "static-responder"
question = '^(\d+)-(\d+)-(\d+)-(\d+)\.rebind\.$'
answer = ["IN A $1.$2.$3.$4"]
[groups.static]
type      = "static-responder"
question  = '^(.+)\.$'
answer    = [ "IN A 0.0.0.0" ]
edns0-ede = { code = 15, text = "IP $1 is in the blocklist" }

Implements #366

Anuskuss commented 8 months ago
Full config ```toml [resolvers.cloudflare] protocol = "dot" address = "1.1.1.1" [groups.rebind] type = "static-responder" question = '^(\d+)-(\d+)-(\d+)-(\d+)\.rebind\.$' answer = ["IN A $1.$2.$3.$4"] [groups.blocklist-resolver] type = "static-responder" question = '^(.+)\.$' answer = [ "IN A 0.0.0.0" ] edns0-ede = { code = 15, text = "IP $1 is in the blocklist" } [groups.blocklist] type = "response-blocklist-ip" resolvers = [ "cloudflare" ] blocklist-resolver = "blocklist-resolver" blocklist = [ "93.184.216.34" ] # example.com [routers.router] routes = [ { resolver = "rebind", name = '\.rebind\.$' }, { resolver = "blocklist", name = '^example\.com\.$' }, { resolver = "cloudflare" } ] [listeners.udp] protocol = "udp" address = ":5300" resolver = "router" [listeners.tcp] protocol = "tcp" address = ":5300" resolver = "router" ```

First part seems to work:

$ dig @127.0.0.1 -p5300 +noall +answer +comments 1-2-3-4.rebind
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 15258
;; flags: qr rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; WARNING: recursion requested but not available

;; ANSWER SECTION:
1-2-3-4.rebind.     3600    IN  A   1.2.3.4

Second part is almost correct but doesn't work how I need it to:

$ dig @127.0.0.1 -p5300 +noall +answer +comments example.com   
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 5193
;; flags: qr rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
; EDE: 15 (Blocked): (IP example.com is in the blocklist)
;; ANSWER SECTION:
example.com.        3600    IN  A   0.0.0.0

The question should ideally be context-aware, meaning if the value came from a listener or e.g. blocklist-v2, the regex would parse the query name but since it came from response-blocklist-ip it should've been the IP address. Also since the IP doesn't end in a dot one can save some memory by just using $0 You'll still have to specify question though (maybe that restriction could be lifted in the future).

Edit: Actually now that I'm thinking about it, it should not strictly be the query name but rather the "reason" why the group got called, e.g. if blocklist-format = "regexp" and blocklist = [ '^.+\.example\.com\.$' ] then ideally it could return something like EDE: 15 (Blocked): (Blocked by "^.+\.example\.com"). I don't wanna feature creep this too much though but I definitely need at least the IP address.

[groups.blocklist-resolver-ip]
type      = "static-responder"
question  = '.+'
edns0-ede = { code = 15, text = "IP $0 is in the blocklist" }

[groups.blocklist-resolver-domain]
type      = "static-responder"
question  = '^(.+)\.$'
edns0-ede = { code = 15, text = "Domain $1 is in the blocklist" }

[groups.blocklist-ip]
type               = "response-blocklist-ip"
blocklist-resolver = "blocklist-resolver-ip"
blocklist          = [ "93.184.216.34" ]

[groups.blocklist-domain]
type               = "blocklist-v2"
blocklist-resolver = "blocklist-resolver-domain"
blocklist-format   = "domain"
blocklist          = [ "example.com" ]

P.S. Also you should probably get rid of that warning at some point (it's probably very low on your todo list).

folbricht commented 8 months ago

It's not possible to pass information about where a query came from to a group, that's by design. Based on your example though, perhaps adding something like edns0-ede = { code = 15, text = "IP $0 is in the blocklist" } directly to the blocklist would make a lot more sense. Giving the blocklist the ability to block and respond with extended error codes.

folbricht commented 8 months ago

The warning should be fixed now too

Anuskuss commented 8 months ago

Based on your example though, perhaps adding something like edns0-ede = { code = 15, text = "IP $0 is in the blocklist" } directly to the blocklist would make a lot more sense.

I think that's a good compromise :+1:

folbricht commented 8 months ago

https://github.com/folbricht/routedns/pull/373 adds edns0-ede to blocklist-v2 directly. It's actually more powerful than just the regex implementation here. I'll probably change this one to match.

Anuskuss commented 4 months ago

Any news on this? I've been using this branch for 4 months now and the first part works great. Maybe you should split this up so the second part can be merged later.

The only things that I can say about the first part is that question needs to match the whole string, so I have to add a superfluous .+ (question='^(\d+)-(\d+)-(\d+)-(\d+)\..+'). If you could lift that restriction that'd save me 2 bytes ;)

folbricht commented 4 months ago

I was thinking of closing this in favor of https://github.com/folbricht/routedns/pull/378. It's already on master. Does that work for your use-case? I imagine it being a bit cleaner than the regex as well

Anuskuss commented 4 months ago

The Go template format is really unfamiliar to me but after a bit of experimentation it seems to work for my use case:

[listeners.udp]
protocol = "udp"
address  = ":5300"
resolver = "static"

[groups.static]
type   = "static-template"
answer = [
  '{{ .Question }} {{ .QuestionClass }} {{ .QuestionType }} {{ replaceAll ( index ( split .Question "." ) 0 ) "-" "." }}'
]
$ dig -p 5300 +noall +answer 1-2-3-4.rebind
1-2-3-4.rebind.     3600    IN  A   1.2.3.4

How is the second part coming along though? I still want to use "reason" in edns0-ede, or at the very least the IP address.

folbricht commented 4 months ago

EDE is actually supported as well, I just didn't have anything in the docs for it yet. Added an example. And I also included your example since it may be useful to others. Try this

[groups.static]
type   = "static-template"
edns0-ede = {code = 15, text = '{{ .Question }} is banned!'}
Anuskuss commented 4 months ago

Yeah but this is "question" - I want the "reason":

[groups.blocklist-resolver]
type      = "static-template"
edns0-ede = { code = 15, text = '"{{ .Reason }}" is banned!' }

[groups.blocklist-ip]
type               = "response-blocklist-ip"
blocklist-resolver = "blocklist-resolver"
blocklist          = [ "93.184.215.14" ] # example.com

[groups.blocklist-v2]
type               = "blocklist-v2"
blocklist-resolver = "blocklist-resolver"
blocklist          = [ '(^|\.)example\.org\.$' ]

Such that example.com returns

; EDE: 15 (Blocked): ("93.184.215.14" is banned!)

while example.org returns

; EDE: 15 (Blocked): ("(^|\.)example\.org\.$" is banned!)

Because what's the point of returning the question to the user? The user already knows the question.

folbricht commented 4 months ago

Unable to test this right now, but you would use the EDE on the blocklist itself for this. Though I don't believe the template has access to the raw blocklist rule that matched (yet). That could be added which would also cover the response blocklist.

[groups.blocklist-v2]
type               = "blocklist-v2"
blocklist-resolver = "blocklist-resolver"
blocklist          = [ '(^|\.)example\.org\.$' ]
edns0-ede = { code = 15, text = '"{{ .Question }}" is banned!' }
Anuskuss commented 4 months ago

Though I don't believe the template has access to the raw blocklist rule that matched (yet).

Yeah.

folbricht commented 3 months ago

@Anuskuss That's now available in https://github.com/folbricht/routedns/pull/403 Would you be able to give it a try? Templates can now access the blocklist name and matching rule.

Anuskuss commented 3 months ago

I think this can be closed now? Or do you want to support both implementations? I might run a benchmark in that case to find out which one is faster (regex vs 3 functions).

folbricht commented 3 months ago

The regex solution is a bit too limiting. I'll close this