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

Include caller arguments and introduce new query type #366

Open
Anuskuss opened this issue Mar 2, 2024 · 17 comments
Open

Include caller arguments and introduce new query type #366

Anuskuss opened this issue Mar 2, 2024 · 17 comments

Comments

@Anuskuss
Copy link

Anuskuss commented Mar 2, 2024

When a group calls another group it would be great to have access to the arguments, e.g.

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

[groups.blocklist-resolver]
type      = "static-responder"
edns0-ede = { code = 15, text = "IP $1 is in the blocklist" } # $1 = "1.2.3.4"

And I would also like to propose the idea of a new query group which allows you to construct your own query (this also needs support for arguments). Could be useful for changing the query type or question, e.g.

[routers.router]
routes = [
  { resolver = "any-emulator", type = "ANY" },
  { resolver = "myresolver" }
]

[groups.any-emulator]
type     = "query"
resolver = "myresolver"
query    = [ "A $2", "AAAA $2" ] # $1 = "ANY", $2 = "example.com"
@folbricht
Copy link
Owner

This isn't possible with the current implementation unfortunately. It's mostly a limitation of the configuration language chosen (TOML). This requires a rewrite of significant parts to support a more flexible way to configure it, possibly using a higher-level language for config. I've been considering doing that but not sure I'll have time for it in the foreseeable future.

@Anuskuss
Copy link
Author

Anuskuss commented Mar 5, 2024

These are mostly gimmicks so it's understandable that it's not a priority. Anyway, I have another example (for people that can't disable their routers' DNS rebind protection):

[routers.router]
routes = [
  { resolver = "dns-rebind-emulator", type = "A", name = '\.rebind\.$' },
  { resolver = "myresolver" }
]

[groups.dns-rebind-emulator]
type      = "replace"
resolvers = [ "dns-rebind-emulator-helper" ]
replace   = [
  { from = '^(\d+)-(\d+)-(\d+)-(\d+)\.rebind\.$', to = '${1}.${2}.${3}.${4}.' }
]

[groups.dns-rebind-emulator-helper]
type   = "static-responder"
answer = [ "IN A $1" ]

Regarding the 2nd suggestion, do you think that's possible with current tools?

@folbricht
Copy link
Owner

If I understand it correctly you need a responder that can build a response based on the query name, right?

This should be possible, and in fact there's a lot of potential here. Let's just make this even more generic. What if we had an element that would allow some simple scripting language to be defined, that could eval the input and either build the output or forward to another group? That'd be extremely powerful and could probably even do what you were asking in part 1 and more.

Just a mockup, but perhaps it could look like

[groups.my-script]
type      = "script-modifier"
resolvers = [ "resolver1", "resolver2" ]
script   = '
  <some script language that can take the input, then either produce a response or forward to any resolver>
'

Would have to find a script interpreter that integrates well, perhaps https://github.com/Shopify/go-lua or https://github.com/yuin/gopher-lua (not used either one yet).

@Anuskuss
Copy link
Author

Anuskuss commented Mar 5, 2024

What does the resolver do in this example? The group takes an input, gives it to the script, the script returns something and that gets forwarded to the resolver? I don't think that's gonna be too useful. I guess preparing something for another group could be useful, but we already got replace and routers, what else could one want?

If scripting were to be introduced (which I don't think is a good idea anyway), I would make it a resolver. So emulating ANY queries would look something like this:

[resolvers.myscript]
type    = "script"
address = "script.sh"

[routers.router]
routes = [
  { resolver = "myscript", type = "ANY" },
  { resolver = "myresolver" }
]
#!/bin/bash

for t in A AAAA CAA HTTPS MX NS SOA TXT; do
  dig +noall +answer $2 $t
done

But then again, I feel like this could almost be done exclusively with the tools already provided.

@cbuijs
Copy link
Contributor

cbuijs commented Mar 6, 2024

Maybe some of this can be done similar as with replace, but then make it possible to modify the the data/answer instead only the query (or even combine)? The variables can be generated using parentheses (round brackets) in regex?

@folbricht
Copy link
Owner

It's definitely possible to implement something like that, a group that takes the query-name, applies a regex, and generates the response IP for it. It's just that this would be very specific to one use-case with a lot of limitations:

  • It would only be able to look at the query name. Not type or TTL etc
  • The response would have to be an A or AAAA
  • The response couldn't include any other records
    Basically it'd be specifically made for this purpose and not allow any flexibility.

With the scripting element I was thinking it'd add a low-level way to do custom modifications. It wouldn't be a shell script of course, but some other language that can inspect and modify queries and responses. In the example above the "resolvers" would be optional. The script would receive a query and could then decide to either answer it directly, or forward to one of the defined resolvers. It'd allow implementing custom routing, load-balancing, etc. Not sure how complex that'd be to implement yet. Perhaps a basic "derive-answer-from-query-name" element would be the right approach for now.

@Anuskuss
Copy link
Author

Anuskuss commented Mar 6, 2024

It's just that scripting is a can of worms. Or rather executing any external file is. Maybe inline scripting is enough, possible in a language that can't do much damage (e.g. Lua)?

Generally speaking, I think a query merger makes sense: Send your query to multiple resolvers (e.g. Cloudflare and Google), collect the answers, remove duplicates and return. That would be the first (technically last) step to emulate ANY queries and would be useful just by itself. I guess changing the query type really isn't useful except for that single use case.

@folbricht
Copy link
Owner

It'd definitely be inline Lua with a small set of pre-defined functions to manipulate DNS queries and responses, no sub-processes or shell access. With that one could then implement pretty much any behavior that may be missing from the standard elements (though a little slower). Implementing this requires a bit more time than I can currently spend.

In the mean time, perhaps I could implement what you asked like so:

[routers.router]
routes = [
  { resolver = "dns-rebind-emulator", type = "A", name = '\.rebind\.$' },
  { resolver = "myresolver" }
]

[groups.dns-rebind-emulator-helper]
type   = "static-responder"
query-name-regex = '^(\d+)-(\d+)-(\d+)-(\d+)\.rebind\.$'
answer = [ "IN A ${1}.${2}.${3}.${4}." ]

This would allow you to use the content of the query name to build a static response. It should solve the original ask. Not a fan of that there being some dynamic behavior in a group named "static-responder" but it's not too bad. This is relatively easy to add. Any thoughts on doing this?

@cbuijs
Copy link
Contributor

cbuijs commented Mar 10, 2024

Doesn't win the beauty contest, but works. :-)

I think for the cases mentioned it should be fine and should be sufficient.

@Anuskuss
Copy link
Author

Looks okay but I don't really like query-name-regex. How about format or question (question being the counterpart to answer)? Also would be nice to be able use this to implement the original request:

[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" }

@folbricht
Copy link
Owner

Would you be able to try out the issue-366 branch? It should let you use a question regex like this:

[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" }

@folbricht
Copy link
Owner

#373 lets you specify an extended error message from the blocklist. And it lets you customize the whole message. Would you be able to try that out?

@folbricht
Copy link
Owner

#378 adds a new static-template that should be able to handle the rebind protection. There's an example of it in the PR. These templates can also use a number of string-manipulation functions that might come in handy. The documentation has been updated.

I think this solution is better than using regexes, and it should be more powerful as it allows customizing all response records, not just the answers.

@Anuskuss
Copy link
Author

The base case works but it breaks when blocklist-resolver is used in response-blocklist-ip (but that may be by design).
Also it seems like there's still no ability to get the filtered IP address instead.

@folbricht
Copy link
Owner

Not sure I understand what exactly fails. Do you have an example config I can try?

@Anuskuss
Copy link
Author

[groups.blocklist-resolver]
type      = "static-responder"
answer    = [ "IN A 0.0.0.0" ]

[groups.blocklist]
type               = "blocklist-v2"
resolvers          = [ "cloudflare" ]
#blocklist-resolver = "blocklist-resolver"
blocklist-format   = "domain"
blocklist          = [ "evil.com" ]
edns0-ede          = { code = 15, text = "{{ .Question }}" }

This example will have EDE: 15 (Blocked): (evil.com.) in the response. Uncommenting blocklist-resolver results in no message.

@folbricht
Copy link
Owner

Yes, that is actually correct/expected. edns0-ede is only used when the blocklist actually blocks something itself. If blocklist-resolver is used, it doesn't actually block but forward somewhere for a response.

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

3 participants