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

Asking kresd about its configuration, notably its configured upstream servers #122

Open
bernd-wechner opened this issue Jan 11, 2025 · 10 comments

Comments

@bernd-wechner
Copy link

I know that I can talk to kresd on a tty. In fact use a small script to do just that:

#!/bin/bash
#
# Opens a tty connection to the knot resolver.
# 
# Assumes only one instance running and connects to that.
#
# Documented here:
#
#	http://knot-resolver.readthedocs.io/en/latest/daemon.html#cli-interface
#
# Just a quick shortcut to getting at CLI for the resolver. 

tty_dir="$(uci get resolver.kresd.rundir)/control"
tty=$(ls -1 $tty_dir | head -1)

if [[ $tty =~ ^-?[0-9]+$ && -a /proc/$tty ]]; then 
	socat - UNIX-CONNECT:$tty_dir/$tty
else
	echo "Looks like kresd is not running"
fi

and I can type lua at the prompt. But it's hard to work out what lua. A simple example is using a discovered tty:

# echo 'modules.list()' | socat - UNIX-CONNECT:/tmp/kresd/control/14898 
> {
    'iterate',
    'hints',
    'validate',
    'cache',
    'ta_update',
    'ta_signal_query',
    'extended_error',
    'priming',
    'detect_time_skew',
    'detect_time_jump',
    'ta_sentinel',
    'edns_keepalive',
    'refuse_nord',
    'watchdog',
    'policy',
    'stats',
    'predict',
}

But what can I send to kresd to answer the question "What are your configured upstream servers?"

I see a wonderful lua example here that is related (but not the same):

#40 (comment)

But I can't send that to this TTY successfully.

I can save that lua script to reset_forwarders.lua and then:

# cat reset_forwarders.lua | socat - UNIX-CONNECT:/tmp/kresd/control/14898 
> 
> /usr/lib/knot-resolver/sandbox.lua:540: [string "function policy:reload_resolv_file()"]:1: 'end' expected near '<eof>'
> /usr/lib/knot-resolver/sandbox.lua:540: [string "  for i = 1, #policy.forwarders do"]:1: 'end' expected near '<eof>'
> [string "return table_print(    print('removing forwar..."]:1: attempt to index a nil value
> [string "return table_print(    policy.del(policy.forw..."]:1: attempt to index a nil value
> /usr/lib/knot-resolver/sandbox.lua:540: [string "  end"]:1: '<eof>' expected near 'end'
> 
> /usr/lib/knot-resolver/sandbox.lua:540: [string "  for line in io.lines(policy.resolv_file) do"]:1: 'end' expected near '<eof>'
> /usr/lib/knot-resolver/sandbox.lua:540: [string "    if not line:match("^%s+#") then"]:1: 'end' expected near '<eof>'
> [string "      local split = string.gmatch(line, "[^%s..."]:1: bad argument #1 to 'gmatch' (string expected, got nil)
> [string "      local name = split()"]:1: attempt to call global 'split' (a nil value)
> [string "      local value = split()"]:1: attempt to call global 'split' (a nil value)
> /usr/lib/knot-resolver/sandbox.lua:540: [string "      if name == 'nameserver' then"]:1: 'end' expected near '<eof>'
> 
> /usr/lib/knot-resolver/kres_modules/policy.lua:45: attempt to concatenate local 'target' (a nil value)
> /usr/lib/knot-resolver/sandbox.lua:540: [string "      end"]:1: '<eof>' expected near 'end'
> /usr/lib/knot-resolver/sandbox.lua:540: [string "    end"]:1: '<eof>' expected near 'end'
> /usr/lib/knot-resolver/sandbox.lua:540: [string "  end"]:1: '<eof>' expected near 'end'
> /usr/lib/knot-resolver/sandbox.lua:540: [string "end"]:1: '<eof>' expected near 'end'
> 
> [string "return table_print(policy:reload_resolv_file())"]:1: attempt to call method 'reload_resolv_file' (a nil value)

Clearly something to do with sandbox.lua and it not supporting this kind of input (and it seems sandbox.lua is driving the interaction).

Which raises two questions:

  1. How is a lua file like that sent to kresd. The issue cited pipes it to sudo nc -U /run/knot-resolver/control but I have kresd on a Turris Omnia (an OpenWRT based router) and we don't have nc (well there's a BusyBox nc on board but it doesn't do that. We do have socat though)

  2. What can I send to it that would reveal the configured upstream servers (or the forwarder configurations)., that kresd is using.

@vcunat
Copy link
Member

vcunat commented Jan 11, 2025

Your command fails because the control socket executes the input line-by-line. That's why the like with function keyword is incomplete (of course it is) and fails to load.

The easiest way is probably to paste such stuff directly into your config file.

But what can I send to kresd to answer the question "What are your configured upstream servers?"

It's impossible to ask that. The configuration constructs generic Lua functions that set some stuff. It's impossible to introspect that, among loads of other advantages. That's price of the flexibility of Lua.

We're now trying to push people from Lua to YAML for 99% of their configs: https://en.blog.nic.cz/2023/12/15/knot-resolver-6-x-news/ But those huge changes haven't reached Turris defaults yet. (I recognize your face from forum.turris.cz.)

On Turris in particular I'd simply look at the /tmp/kresd.config file, unless you're changing the forwarders through the control socket already.

@bernd-wechner
Copy link
Author

Wow, thanks, it's in /tmp/kresd.config!

Interesting though that introspection isn't possible. Given this code is cited in the the issue I linked to aove:

  for i = 1, #policy.forwarders do
    print('removing forwarder rule:', policy.forwarders[i].id)
    policy.del(policy.forwarders[i].id)
  end

So they are printing policy.forwarders[i].id which suggested to me that you could print other properties of policy.forwarders[i]?

@vcunat
Copy link
Member

vcunat commented Jan 11, 2025

Sure, a little information is there, but you can't show lua closures/functions and what you need is hidden inside those.

@vcunat
Copy link
Member

vcunat commented Jan 11, 2025

What the policy config is that you construct some functions, store them in this array and then execute them on every request. It's too generic; those functions could do anything.

@bernd-wechner
Copy link
Author

bernd-wechner commented Jan 11, 2025

Three quick questions while we're here (because I am always curious):

  1. Your command fails because the control socket executes the input line-by-line. That's why the like with function keyword is incomplete (of course it is) and fails to load.

In conclusion, the socket can only process lua that is expressible on one line of lua (it even fails if I write a function on one line echo 'for i = 1, #policy.forwarders do; print(policy.forwarders[i].id);; end' | socat - UNIX-CONNECT:/tmp/kresd/control/14898 for example. So can't handle the semicolon.

I also see in exander77's sample that yes he defines the function in the config (where would he put that?) and then uses a one-liner to call it on the socket.

  1. Where does /tmp/kresd.config sit in the boot sequence. Is this a file written by some other agent when starting kresd? If so what is it? Or is it a file kresd writes itself to express what configuration it received one startup? (ever curious here). Mind you that's more a Turris OS question than a kresd question and perhaps out of context here.

  2. In the config I see:

# cat /tmp/kresd.config 
--Automatically generated file; DO NOT EDIT
modules = {
    'hints > iterate'
  , 'policy'
  , 'stats'
  , predict = { window = 0, period = 0 }
}
hints.use_nodata(true)
policy.add(policy.rpz(policy.DENY, '/etc/kresd/adb_list.overall'))
hints.config('/tmp/kresd/hints.tmp')
net.listen('0.0.0.0', 53, { kind = 'dns' })
net.listen('0.0.0.0',   853, { kind = 'tls' })
net.listen('::', 53, { kind = 'dns' })
net.listen('::',   853, { kind = 'tls' })
trust_anchors.remove('.')
trust_anchors.add_file('/etc/root.keys', true)
net.bufsize(1232)
net.ipv4=true
net.ipv6=true
cache.open(20*MB)
table.insert(policy.special_names, { count = 0, cb = policy.all(
policy.TLS_FORWARD(
{{'9.9.9.9'
,hostname='dns.quad9.net'
,ca_file='/etc/ssl/certs/ca-certificates.crt'
},{'149.112.112.112'
,hostname='dns.quad9.net'
,ca_file='/etc/ssl/certs/ca-certificates.crt'
},{'2620:fe::fe'
,hostname='dns.quad9.net'
,ca_file='/etc/ssl/certs/ca-certificates.crt'
},{'2620:fe::9'
,hostname='dns.quad9.net'
,ca_file='/etc/ssl/certs/ca-certificates.crt'
}}))})
user('kresd','kresd')

the two IPv6 addresses puzzle me. I'm informed that '2620:fe::fe' and '2620:fe::9' are IPv6 ULAs (Unique Local Addresses, tha apparent IPv6 equivalent of 19.168.. and 10...* in the IPv4 world? Why would quad9.net be providing those? Or have htose configured? Or have I misunderstood Ipv6? (easy to imagine, I'm new to it).

@vcunat
Copy link
Member

vcunat commented Jan 12, 2025

  1. I believe that arbitrary lua expressions can be used via the control socket by use of semicolons and the like, but I don't clearly recall relying on that and I don't use such compressed syntax anyway.

  2. The init script produces that config.

  3. No. Those are normal public IPv6 addresses. I usually look around here for reference: https://en.wikipedia.org/wiki/IPv6_address#Unicast_addresses

@vcunat
Copy link
Member

vcunat commented Jan 12, 2025

I also see in exander77's sample that yes he defines the function in the config (where would he put that?) and then uses a one-liner to call it on the socket.

The configuration file and control socket commands are just a lua scripts that get executed in the global context that lives as long as the daemon. You can do whatever in there, e.g. define functions.

@vcunat
Copy link
Member

vcunat commented Jan 12, 2025

I forgot to mention that on Turris you can add custom parts to the configuration like this: https://wiki.turris.cz/en/public/dns_knot_misc#adding_your_own_custom_configuration_file_to_knot

@bernd-wechner
Copy link
Author

2. The init script produces that config.

Been trying to answer a curious question and upskill some too, by browsinghte codebase. It's not easy, that's for sure. But what I can see is that kresd is run as a procd service. Alas the openwrt boot strap and procd are both poorly documented, in bits only with warning even i places.

What I can divine is kresd is loaded with a start_service call which invokes run_instance:

run_instance() {
	procd_open_instance
	procd_set_param file /etc/config/resolver
	procd_set_param command "$PROG"
	procd_append_param command --noninteractive
	procd_set_param nice '-5'
	procd_set_param respawn
	modify_rundir
	init_header
	load_uci_config_kresd
	load_uci_config_common

	echo "user('$USERNAME','$GROUP')" >> $CONFIGFILE

	procd_close_instance
	[ \! -x "$(which at)" ] || echo "/etc/init.d/kresd test_ipv6" | at now+2min 2>&1 | fgrep -v 'warning: commands will be executed using /bin/sh'
}

But that's where docs and ability to test ona live syustem fail me. I may soon have my old omni reflashed and ready forrunning offline tests. Stlll in the mean time, what I couldn't find out here is when kresd is actuallyrun and trying to answer for myself the curious question: Is /tmp/kresd.config written before kresd is started and read and loaded by kresd when it starts, or is it written after kresd starts and just an FYI.

I had hoped to be able to answer that myself but have thus far failed. The procd function schema is a little convoluted and even then I can't find clearly where it acts on the command param that was set.

My inference is that run_instance does no such thing and that the role of start_service is to define the parameters that allow procd to actually start the service. Or possibly even that procd_close_instance is responsible for closing off the defintion and asking procd to start the service because there's a line imemdiately thereafter that assumes it's up and waits 2 mins to be sure of it.

In which case /tmp/kresd.resolver is written before it starts but I can't see that $procd is passed $CONFIGFILE as a apram and so it's possible that the kresd binary just looks at /tmp/kresd.conf on a hardcoded basis.

Not obvious from a codebase search:

https://github.com/search?q=repo%3ACZ-NIC%2Fknot-resolver%20kresd.conf&type=code

Could be a Turris customisation, not also not obviously:

https://github.com/search?q=org%3Aturris-cz+%2Ftmp%2Fkresd.conf&type=code

Thanks enormously BTW for the help. And yes, I have since learned more about IPv6 and was misled. They just happen to be terse, because of long 0 strings in the expanded address, but they resolve with whois quite well.

@vcunat
Copy link
Member

vcunat commented Jan 12, 2025

I believe the init script produces the config file ($CONFIGFILE) and then starts kresd process to consume that file.

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