JSON on OpenWrt

Hi all

I'd like to discuss the issue of JSON. I'm noticing that some API's employ this format (mainly thinking of ubus here). I understand that it may be convenient to emit data in this format. I also understand that this is a decent way to store and transfer complex data structures. At the same time, parsing JSON with the built-in command-line tools on OpenWrt is far from convenient and takes quite a bit of code (if using jshn), and/or requires performance sacrifices (if using jsonfilter), both of which I think in some cases could be avoided.

I'm not familiar enough with most of what's going on in ubus but recently I was dealing with parsing JSON data emitted by the command service dnsmasq info. I believe this command outputs a subset of what you can get from ubus call service list. All I actually wanted to get from it was a list of existing dnsmasq instances and some properties of each instance: index, name, conf-dir, running, interfaces. This could be easily provided by a simple API, e.g.

dnsmasq_get_instances INSTANCES
for instance in $INSTANCES; do
    dnsmasq_get_instance INDEX RUNNING IFACES CONFDIR "$instance"
    <do_actually_useful_stuff>
done

However the data is only available as JSON (and some of it not even included in the data structure but rather has to be pulled from a file).

So I ended up with this:

get_dnsmasq_instances() {
	local nonempty='' instance instances instance_index l1_conf_file l1_conf_files conf_dirs conf_dirs_cnt IFS_OLD i s f dir
	DNSMASQ_INSTANCES=
	DNSMASQ_INSTANCES_CNT=0
	reg_action -blue "Checking dnsmasq instances."
	. /usr/share/libubox/jshn.sh
	json_load "$(/etc/init.d/dnsmasq info)" &&
	json_get_keys nonempty &&
	[ -n "${nonempty}" ] &&
	json_select dnsmasq &&
	json_select instances &&
	json_get_keys instances || return 1

	instance_index=0
	for instance in ${instances}
	do
		json_is_a "${instance}" object || continue # skip if $instance is not object
		json_select "${instance}" &&
		json_get_var "${instance}_RUNNING" running &&
		json_is_a command array &&
		json_select command || return 1

		add2list DNSMASQ_INSTANCES "${instance}" || return 1
		eval "${instance}_INDEX=${instance_index}"
		instance_index=$((instance_index+1))
		l1_conf_files=

		# look for '-C' in values, get next value which is instance's conf file
		i=0
		while json_is_a $((i+1)) string
		do
			i=$((i+1))
			json_get_var s ${i}
			[ "${s}" = '-C' ] || continue
			json_get_var l1_conf_file $((i+1)) || return 1
			add2list l1_conf_files "${l1_conf_file}" || return 1
		done
		json_select ..
		json_select ..

		IFS_OLD="$IFS"
		# get ifaces for instance
		IFS="${_NL_}"
		ifaces="$(
			for f in ${l1_conf_files}
			do
				$SED_CMD -nE '/^\s*interface=/{s/.*=//;p;}' "${f}"
			done | $SORT_CMD -u | $SED_CMD -z 's/\n/, /g'
		)"
		eval "${instance}_IFACES=\"${ifaces%, }\""

		# get conf-dirs for instance
		conf_dirs="$(
			for f in ${l1_conf_files}
			do
				$SED_CMD -n '/^\s*conf-dir=/{s/.*=//;/[^\s]/p;}' "${f}"
			done | $SORT_CMD -u
		)"		

		for dir in ${conf_dirs}
		do
			add2list ALL_CONF_DIRS "${dir}"
		done
		IFS="${IFS_OLD}"
		eval "${instance}_CONF_DIRS=\"${conf_dirs}\""

		cnt_lines conf_dirs_cnt "${conf_dirs}"
		eval "${instance}_CONF_DIRS_CNT=\"${conf_dirs_cnt}\""
	done
	IFS="${IFS_OLD}"
	json_cleanup
	cnt_lines DNSMASQ_INSTANCES_CNT "${DNSMASQ_INSTANCES}"

	:
}

Which leads me to think: why? And also, how many other similar cases of simple API replaced by JSON are there?

So specifically for this case of dnsmasq instances, I've been thinking that perhaps it makes sense to add the simple API to the dnsmasq init script. I could even go ahead and implement that, but I'm not sure if the result has any chance to get merged.

And in general, I'd like to hear what other people think about this.

I started to chat about this with @efahl in another topic but a separate topic is more appropriate for this discussion.

the C ubox api is well suited for this ... the problem is there is no real documentation. I have done lots of stuff like this using the api, its great for parsing json data, and to read/parse the contents of a files is straight forward in C. the api allows you to merged the data(your ubus json resp + your init.d file contents) into a single json response ... or to what ever format you need (file, struct, object). I may add a few examples if there is any interest

1 Like

I agree, the API is functional and you can get things done with it. But consider the lines count in the above 2 code snippets, and how much time I had to put into writing each (20s vs at least an hour). I think this is a case of creating complications and then solving them with great satisfaction.

Also not everyone is writing in C. Lots of OpenWrt system code is shell code. So this is not going to get compiled into a tiny binary. Which is another reason I'd rather have the simple API available, which I could use to get the same data in 5 lines of code, rather than 25.

Adding examples is always good, and if they have code comments then jshn wiki may benefit from them as well.

yeah, this is a common mindset sadly ... hence why i didn't bother to include an example. But what you and many others are missing, is that with a few lines of code and a couple minutes of time, you could add this functionality to the ubus method, in such a way that when you make the the call service dnsmasq info , you receive all the information you are after ... in any format you desire. Not to mention you'd likely have a much easier time to get it merged, assuming there's any utility to it.

Personally, I'm not missing this - I just don't know C. So for me, this would be a couple of years of learning C and then a couple of minutes time.

So yeah, skill issue. However, I can make useful things with what I know. Same applies to many other people around here. People make things because they are useful, such as (for example) every single one of existing DNS-based adblocking solutions on OpenWrt (perhaps except AdGuard Home which I didn't really check the code of). This is the reality, and IMO the reality should be accommodated.

(P.s. for applications on OpenWrt, shell code in some sense is superior to C, as it has much less version-specific dependencies, so you can write code that will work across many OpenWrt versions)

3 Likes

long live shell

1 Like

The potential sounds good in theory, but after wasting a full day on it I came to the conclusion you have to be a member of the "Secret Society of Obfuscated Software" to get anywhere. Life is too short as it is. /s

As for parsing json, as you do, I invariably end up writing a dedicated script for the specific job (and I like your ideas on script efficiency - so do take notice, even if I do not totally agree with you every time).

1 Like