Watchcat should be able to restart the radio

Hi! I’m not sure why but one of my OpenWRT routers occasionally gets hung up and needs to have the radio restarted (LuCI → Network → Wireless →Radio0: Restart). The description of watchcat made it sound like it could do that for me automatically, but it doesn’t. After reading the source code, it turns out watchcat’s “Restart” option does an "ifup” on the network interface. What I’m looking for is an option to restart the radio using the wifi command.

Running /sbin/wifi with no arguments restarts all radios. I’d expect watchcat to only restart the correct radio (/sbin/wifi up radio0)To find the radio associated with the network interface that is failing to ping, one can use the wifi status command which returns JSON similar to this: radio0 { ifname: wlan0 } radio1 { ifname: wlan1 }.

I’ll try using watchcat’s “Run script” and see if that works well enough for my needs.

watchcat itself is simply a shell script, so you can hack it up to do whatever you like. The run_script function looks about as general as it gets, so yeah, give it a try. Once you've got a script, make sure to add it to your backups in /etc/sysupgrade.conf, so it's retained over upgrades.

Thank you, especially for the tip about sysupgrade.conf. It was super easy to hack and appears to be working. (Crosses fingers).

I’m now looking into if I could make a proper patch for the script that would be usable by the wider community. I’ve run in to a small snag, though. It looks like OpenWRT doesn’t come with gron, which is one easy way to map from the interface to the radio name. For example:

$ wifi status | gron | grep wlan0
json.radio0.interfaces[0].ifname = "wlan0";
json.radio0.interfaces[1].ifname = "wlan0-1";
$ wifi status | gron | grep wlan0 | grep -Eom1 'radio[^.]+'
radio0

I’d rather not add any unnecessary dependencies, so how does one do something similar using just OpenWRT’s default tools? (I already tried jsonfilter, but it looks like JSONPath doesn't allow one to select the parent elements of a match. Weird.)

Can you look at https://github.com/openwrt/packages/pull/29080 and see if that helps or gives some useful insight?

Thanks, but I think that’s a different issue as it is still talking about ifup. The fix there of using find_config wlan0 just switches ifup to use wwan. Watchcat already lets me select wwan to restart, which didn't help my situation.

I need radio0 to be restarted. I’m not sure if OSI terminology is helpful here, but that pull request describes ifup as working at layer 2 (data link), while I’m talking about layer 1 (physical).


One potentially useful thing from that pull request is that it refers to /lib/network/config.sh which points to some JSON parsing functions in /usr/share/libubox/jshn.sh. They are quite a bit more complex than gron.

Can someone here help me figure out how to use them to convert an interface name like “wlan0” to a device name like “radio0”? I got as far as

source /usr/share/libubox/jshn.sh
jsonload “$(wifi status)”
json_get_keys _v_keys
json_select radio0

but I’m not sure where to go from there. I must be doing something wrong as there has to be an simpler way than this.

Do any of the functions in /lib/functions/network.sh help you?

Given the wireless interface name, you want the radio device?

Try this one liner:

device=$(iw dev "$ifname" info | grep -w "wiphy" | awk '{printf "radio%s", $2}')

Something like this?

$ wifi status | jsonfilter -e '$[*].interfaces[@.ifname = "phy1-ap0"].config.device[*]'
radio1

Yay, mine is 5 characters shorter than yours :nerd_face: :grin:

Yours could be even shorter and drop one of the process invocations, let awk do the pattern match:

iw dev "$ifname" info | awk '/\<wiphy\>/ {printf "radio%s", $2}'

(Are the device names always hard-coded radioN? Seems brittle...)

Here's some more grist for the mill.

#!/bin/sh

eval $(ubus call iwinfo devices | jsonfilter -e 'devices=$.devices[*]')
for device in $devices; do
        eval $(wifi status | jsonfilter -e 'radio=$[*].interfaces[@.ifname = "'$device'"].config.device[*]')
        printf "device=%s radio=%s\n" "$device" "$radio"
done

eval $(wifi status | jsonfilter -e 'radios=$')
for radio in $radios; do
        eval $(wifi status \
                | jsonfilter -e '$.'$radio'.interfaces[0]' \
                | jsonfilter \
                        -e 'device=$.config.device[*]' \
                        -e 'ifname=$.ifname' \
                        -e 'section=$.section' \
        )
        printf "radio=%s device=%s ifname=%s section=%s\n" "$radio" "$device" "$ifname" "$section"
done

Yes, another six characters! :nerd_face:
Could leave out the double quotes round $ifname...... :scream:

The word radio seems to be a tradition in OpenWrt, but can be any alphanumeric string as far as I can tell. The integer at the end is the phyindex and that, I think, comes from the nl80211 based wireless driver support in the kernel...

It might be more consistent these days to use the word "phy"....

Lets list the wireless device names:

root@meshnode-8ecb:~# echo "$(uci show wireless | grep "path" | awk -F "." '{printf "%s ", $2}')"
radio0 radio1 
root@meshnode-8ecb:~# 

Related to all this, as well as being interesting, for wifi status on my dev/test system, I get:

{
	"radio0": {
		"up": true,
		"pending": false,
		"autostart": true,
		"disabled": false,
		"retry_setup_failed": false,
		"config": {
			"disabled": false,
			"type": "mac80211",
			"band": "2g",
			"beacon_int": 50,
			"channel": "1",
			"country": "AD",
			"htmode": "HE40",
			"log_level": 3,
			"noscan": true,
			"path": "platform/soc/18000000.wifi"
		},
		"interfaces": [
			{
				"section": "default_radio0",
				"config": {
					"network": [
						"lan"
					],
					"device": [
						"radio0"
					],
					"mode": "ap",
					"disabled": false,
					"encryption": "owe",
					"ifname": "phy0-ap0",
					"key": "owe_null_key",
					"macaddr": "96:83:c4:a5:8e:cb",
					"ssid": "MeshGate-2g-8ecb",
					"radios": [
						
					]
				},
				"ifname": "phy0-ap0",
				"vlans": [
					
				],
				"stations": [
					
				]
			},
			{
				"section": "vxradio0",
				"config": {
					"network": [
						"vtunlan"
					],
					"device": [
						"radio0"
					],
					"mode": "ap",
					"disabled": false,
					"encryption": "owe",
					"ifname": "vxradio0",
					"key": "owe_null_key",
					"macaddr": "96:83:c4:a7:8e:cb",
					"ssid": "VTunnel-2g-8ecb",
					"radios": [
						
					]
				},
				"ifname": "vxradio0",
				"vlans": [
					
				],
				"stations": [
					
				]
			},
			{
				"section": "m11s0",
				"config": {
					"network": [
						"lan"
					],
					"device": [
						"radio0"
					],
					"mode": "mesh",
					"disabled": false,
					"dtim_period": 2,
					"encryption": "sae",
					"ifname": "m-11s-0",
					"key": "78c8068012f8481fec118451e1041b3751801a24ab3e222643a0a6a4424b82a1",
					"macaddr": "96:83:c4:a3:8e:cb",
					"mesh_id": "92d490daf46cfe534c56ddd669297e",
					"mesh_rssi_threshold": -68,
					"radios": [
						
					]
				},
				"ifname": "m-11s-0",
				"vlans": [
					
				],
				"stations": [
					
				]
			}
		]
	},
	"radio1": {
		"up": true,
		"pending": false,
		"autostart": true,
		"disabled": false,
		"retry_setup_failed": false,
		"config": {
			"disabled": false,
			"type": "mac80211",
			"band": "5g",
			"channel": "36",
			"country": "AD",
			"htmode": "HE80",
			"log_level": 3,
			"path": "platform/soc/18000000.wifi+1"
		},
		"interfaces": [
			{
				"section": "default_radio1",
				"config": {
					"network": [
						"lan"
					],
					"device": [
						"radio1"
					],
					"mode": "ap",
					"disabled": false,
					"encryption": "owe",
					"ifname": "phy1-ap1",
					"key": "owe_null_key",
					"macaddr": "96:83:c4:a6:8e:cb",
					"ssid": "MeshGate-5g-8ecb",
					"radios": [
						
					]
				},
				"ifname": "phy1-ap1",
				"vlans": [
					
				],
				"stations": [
					
				]
			},
			{
				"section": "vxradio1",
				"config": {
					"network": [
						"vtunlan"
					],
					"device": [
						"radio1"
					],
					"mode": "ap",
					"disabled": false,
					"encryption": "owe",
					"ifname": "vxradio1",
					"key": "owe_null_key",
					"macaddr": "96:83:c4:a8:8e:cb",
					"ssid": "VTunnel-5g-8ecb",
					"radios": [
						
					]
				},
				"ifname": "vxradio1",
				"vlans": [
					
				],
				"stations": [
					
				]
			}
		]
	}
}

But your "grist for the mill" gives me:

root@meshnode-8ecb:~# #!/bin/sh
root@meshnode-8ecb:~# 
root@meshnode-8ecb:~# eval $(ubus call iwinfo devices | jsonfilter -e 'devices=$.devices[*]')
root@meshnode-8ecb:~# for device in $devices; do
>         eval $(wifi status | jsonfilter -e 'radio=$[*].interfaces[@.ifname = "'$device'"].config.device[*]')
>         printf "device=%s radio=%s\n" "$device" "$radio"
> done
device=phy1-ap1 radio=radio1
device=vxradio0 radio=radio0
device=phy0-ap0 radio=radio0
device=vxradio1 radio=radio1
device=m-11s-0 radio=radio0
root@meshnode-8ecb:~# 
root@meshnode-8ecb:~# eval $(wifi status | jsonfilter -e 'radios=$')
root@meshnode-8ecb:~# for radio in $radios; do
>         eval $(wifi status \
>                 | jsonfilter -e '$.'$radio'.interfaces[0]' \
>                 | jsonfilter \
>                         -e 'device=$.config.device[*]' \
>                         -e 'ifname=$.ifname' \
>                         -e 'section=$.section' \
>         )
>         printf "radio=%s device=%s ifname=%s section=%s\n" "$radio" "$device" "$ifname" "$section"
> done
radio=radio0 device=radio0 ifname=phy0-ap0 section=default_radio0
radio=radio1 device=radio1 ifname=phy1-ap1 section=default_radio1
root@meshnode-8ecb:~# 

I have numerous ifnames, something not right...

That's just using the first of the interfaces at [0], so would need some iteration to produce all of them.

# basename "$(readlink -f /sys/class/net/phy1-ap0/phy80211)"
phy1

then map phyX to radioX

Here ya go:

eval $(wifi status | jsonfilter -e 'radios=$')
for radio in $radios; do
        wifi status | jsonfilter -e '$["'"$radio"'"].interfaces[*]' | while read config; do
                eval $(jsonfilter -s "$config" \
                        -e 'device=$.config.device[*]' \
                        -e 'ifname=$.ifname' \
                        -e 'section=$.section' \
                )
                printf "radio=%s device=%s ifname=%s section=%s\n" "$radio" "$device" "$ifname" "$section"
        done
done

Using your json above, gives:

radio=radio0 device=radio0 ifname=phy0-ap0 section=default_radio0
radio=radio0 device=radio0 ifname=vxradio0 section=vxradio0
radio=radio0 device=radio0 ifname=m-11s-0 section=m11s0
radio=radio1 device=radio1 ifname=phy1-ap1 section=default_radio1
radio=radio1 device=radio1 ifname=vxradio1 section=vxradio1

@cshoredaniel: I couldn't find anything in /lib/functions/network.sh that helped.

@efahl: That was much more complicated than I thought. Thank you for figuring it out!

It is too bad jsonfilter is so limited. If OpenWRT ever decides to include gron, this will become much simpler. For example, using bluewavenet's wifi status output,

$ wifi status | gron | grep config.ifname
json.radio0.interfaces[0].config.ifname = "phy0-ap0";
json.radio0.interfaces[1].config.ifname = "vxradio0";
json.radio0.interfaces[2].config.ifname = "m-11s-0";
json.radio1.interfaces[0].config.ifname = "phy1-ap1";
json.radio1.interfaces[1].config.ifname = "vxradio1";

That would give you a one-liner similar to @bluewavenet's initial response, like this,

$ device=$(wifi status | gron | grep "config\.ifname = \"$ifname\"" | cut -d. -f2)
$ echo $device
radio0

Or, if you wanted to be fancy,

$ wifi status | gron | awk -F'[.;]| = '  \
  '$3 ~ /interfaces.*/ && $4=="ifname" {printf("device[%s]=\"%s\"\n", $5, $2);} '
device["phy0-ap0"]="radio0"
device["vxradio0"]="radio0"
device["m-11s-0"]="radio0"
device["phy1-ap1"]="radio1"
device["vxradio1"]="radio1"

But, until that time, @efahl's code will work in watchcat.

Oh yeah, there are other tools that make that code much more elegant, but jsonfilter has a couple attributes that make it the go-to parser for this sort of thing: It's only 24kB in size (and since it piggybacks on a json lib that is common to a bunch of base functionality, that's it); it's guaranteed to be on all OpenWrt devices, as it's required by base-files, so no extra dependencies.

Puts grumpy old git troll hat on
Pfft, fancy nancy json :rofl:

What's wrong with good old fashioned parsing iw output:

iw dev "$ifname" info | grep -w "wiphy" | awk '{printf "radio%s", $2}'

removes hat

All good :clinking_beer_mugs:

it says

Do NOT screenscrape this tool, we don't consider its output stable.

:rofl: I didn't screen scrape it, I parsed it.
And anyway, that message has been there since 1864 when Queen Victoria signed off the first beta version on the basis the output might have been a little unstable and in a state of continuous development. It is now at version 6.17+ and the days of unstable output are long gone.