alfred: add new package - #30161
Conversation
|
@BKPepe I think we had that before. @simonwunderlich is the maintainer - as stated in the Makefile |
|
Right, sorry — fixed the PR description, @simonwunderlich is the maintainer as stated in the Makefile. Same for batctl (#30163) and batman-adv (#30164). |
openwrt-ai
left a comment
There was a problem hiding this comment.
The Makefile cleanup looks right: the define blocks use the indentation each kind actually needs (two spaces for the metadata block, tabs for install and config, none for conffiles), PKG_RELEASE:=1 is correct for a new package, PKG_MAINTAINER is set, and BuildPackage is called with the literal name rather than $(PKG_NAME).
Everything I flagged is a carry-over from the routing feed rather than something this PR introduces, so none of it blocks the move. The two worth a look — before or in a follow-up — are the loadstring on mesh-sourced data and the unchecked io.open in bat-hosts.lua; the second one is a plain crash on any node with the bonding kmod loaded. The rest are marked nit:.
Two things I could not check from here, flagging so nobody assumes they were verified: PKG_HASH and the existence of the 2026.2 tarball (downloads.open-mesh.org is not reachable in this environment), and byte-for-byte parity with the current routing-feed master (no access to openwrt/routing). CI on the head commit is still pending, so there is nothing to read from it yet.
Generated by Claude Code
| local output = fd:read("*a") | ||
| fd:close() | ||
| if output and output ~= "" then | ||
| assert(loadstring("rows = {" .. output .. "}"))() |
There was a problem hiding this comment.
loadstring executes the output of alfred -r 64 as Lua code, and that output is assembled from payloads published by every other node on the mesh. If a remote node can get a " or a \ through alfred's escaping into a type-64 payload, it breaks out of the string literal and runs arbitrary Lua as root on every node with PACKAGE_ALFRED_BATHOSTS enabled — and this script is re-run from cron every 5 minutes, see the crontab line installed at alfred.init:81.
alfred's client does escape quotes/backslashes/non-printables as \xNN when it prints the { "mac", "data" } records, so this is probably safe in practice — but it makes a remote-input trust boundary depend entirely on the exact escaping of another program, with no local check. Parsing the records with a string.gmatch pattern (the MAC is already validated with ^%x%x:... at line 44) instead of evaluating them would remove the question entirely.
Is relying on alfred's escaping here deliberate?
Generated by Claude Code
| function get_interface_address(name) | ||
| local addressfile = io.open("/sys/class/net/"..name.."/address", "r") | ||
| local ret_string = addressfile:read() | ||
| addressfile:close() | ||
| return ret_string | ||
| end |
There was a problem hiding this comment.
io.open is not checked before addressfile:read() is called on it. /sys/class/net/ does not contain only interfaces: with the bonding kmod loaded it also contains the plain file bonding_masters, which the ls -1 at line 15 happily returns and which has no address below it. io.open then yields nil, nil:read() raises "attempt to index a nil value", and the whole facter dies — so no bat-hosts data gets published at all on such a node.
| function get_interface_address(name) | |
| local addressfile = io.open("/sys/class/net/"..name.."/address", "r") | |
| local ret_string = addressfile:read() | |
| addressfile:close() | |
| return ret_string | |
| end | |
| function get_interface_address(name) | |
| local addressfile = io.open("/sys/class/net/"..name.."/address", "r") | |
| if not addressfile then return nil end | |
| local ret_string = addressfile:read() | |
| addressfile:close() | |
| return ret_string | |
| end |
Returning nil needs a matching guard at the caller: line 40 would otherwise do ifaces[nil] = i, which raises "table index is nil". if address and not ifaces[address] then ifaces[address] = i end covers it.
get_hostname at line 6 has the same unchecked-io.open shape, though /proc/sys/kernel/hostname is always there in practice.
Generated by Claude Code
| config_get batmanif "$section" batmanif | ||
| append args "-b $batmanif" | ||
|
|
||
| append alfred_args "$args" |
There was a problem hiding this comment.
config_foreach runs alfred_start for every alfred section, but the results are accumulated into one flat alfred_args that feeds a single procd instance. Two enabled sections therefore produce alfred -f -i eth0 -m -b bat0 -f -i eth1 -b bat1 — one daemon with repeated -i/-b, not two daemons. vis_args at line 52 has the same shape, and run_facters ends up holding whatever the last section set.
Is a single section the only supported configuration? If so, opening one procd instance per section inside alfred_start (instance name derived from $section) would make the multi-section case actually work, or the config_foreach could go away in favour of a single named section so the limitation is visible in the script.
Generated by Claude Code
| alfred_start() { | ||
| local args="" | ||
| local section="$1" | ||
| local disabled interface mode |
There was a problem hiding this comment.
nit: batmanif and start_vis are used only inside alfred_start (lines 43-52) but are not declared local, unlike their neighbours on this line, so they leak into the rc.common shell and carry over between sections.
| local disabled interface mode | |
| local disabled interface mode batmanif start_vis |
run_facters has to stay global — start_service reads it at line 78.
Generated by Claude Code
| # REMOVE THIS LINE TO ENABLE ALFRED | ||
| option disabled '1' |
There was a problem hiding this comment.
nit: "THIS LINE" reads as the comment itself, but deleting the comment does nothing — it's the option disabled '1' below that has to go.
| # REMOVE THIS LINE TO ENABLE ALFRED | |
| option disabled '1' | |
| # REMOVE THE LINE BELOW TO ENABLE ALFRED | |
| option disabled '1' |
Generated by Claude Code
|
|
||
| define Package/alfred/conffiles | ||
| /etc/config/alfred | ||
| /etc/alfred/bat-hosts.lua |
There was a problem hiding this comment.
nit: this entry is unconditional, but the file is only installed when CONFIG_PACKAGE_ALFRED_BATHOSTS is set (lines 84-85), and it goes in via $(INSTALL_BIN) as an executable facter script rather than as a config file.
Nothing breaks — the packaging step skips conffiles that aren't in the image (package-pack.mk: [ -f $(IDIR)/$file ] || continue) — so this is only worth confirming the entry is intentional rather than a leftover.
Generated by Claude Code
openwrt-ai
left a comment
There was a problem hiding this comment.
Reviewed the one new commit since 0ef968b (alfred: handle unreadable files in bat-hosts.lua). The commit message matches what the diff does, and the get_interface_address path is now correct — nil is returned and the caller skips those entries, so a node with the bonding kmod loaded no longer kills the facter.
One loose end: the get_hostname guard has no caller-side counterpart, so it trades "attempt to index a nil value" for "attempt to concatenate a nil value" a few lines later. Inline comment with a suggestion. Not a blocker — /proc/sys/kernel/hostname is always present in practice, same as before this commit.
The earlier threads on loadstring, the init script's single-instance config_foreach, and the nit: items are untouched by this commit and still open; nothing to add there.
CI on 2bb2c4d is green across all ten build targets and all three FormalityCheck jobs. Still not verified from here: PKG_HASH / the 2026.2 tarball (downloads.open-mesh.org unreachable in this environment) and byte-for-byte parity with the current routing-feed master.
Generated by Claude Code
| local n, i | ||
| local ifaces, ret = {}, {} | ||
|
|
||
| local hostname = get_hostname() |
There was a problem hiding this comment.
The get_interface_address half of this fix is complete — nil is returned and the caller now skips it. The get_hostname half is not: the new if not hostfile then return nil end at line 7 has no matching guard here, so hostname can be nil and mac.." "..hostname.."_"..iname at line 49 raises "attempt to concatenate a nil value". The guard converts one crash into a different crash rather than removing it.
Same applies when the file opens but is empty — hostfile:read() returns nil too, which the if not hostfile check does not cover.
| local hostname = get_hostname() | |
| local hostname = get_hostname() | |
| if not hostname then return ret end |
Bailing out with the empty ret means publish_bat_hosts writes an empty chunk instead of dying, which matches how the rest of the script degrades.
Generated by Claude Code
The package has been moved to the openwrt/packages feed, as discussed in openwrt#184. See openwrt/packages#30161. Signed-off-by: Josef Schlehofer <pepe.schlehofer@gmail.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
alfred is a user space daemon for distributing arbitrary local information (e.g. hostnames or vis data) over a batman-adv mesh network in a decentralized fashion via IPv6 link-local multicast. Moved from the openwrt/routing feed, as discussed in openwrt/routing#184. Signed-off-by: Josef Schlehofer <pepe.schlehofer@gmail.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
get_interface_address() calls io.open() and dereferences the result without checking it. /sys/class/net does not contain interfaces only: with the bonding module loaded it also holds the plain file bonding_masters, which the `ls -1` in get_interfaces_names() returns and which has no address below it. io.open() then returns nil and the script dies with "attempt to index a nil value", so no bat-hosts data is published at all on such a node. Return nil when the file cannot be opened and skip those entries in the caller, which would otherwise index the interface table with nil. get_hostname() gets the same guard. Reported-by: openwrt-ai[bot] Signed-off-by: Josef Schlehofer <pepe.schlehofer@gmail.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Adds alfred from the openwrt/routing feed — the routing packages are being moved into openwrt/packages one by one, as discussed in openwrt/routing#184.
Includes the Makefile cleanup pending in openwrt/routing#1192.
The content matches the current routing feed master. Once this is merged, the package will be removed from the routing feed (a coordinated removal PR is prepared there).
Maintainer: @simonwunderlich