Skyweave Custom API
Summary: This session focused on advancing the reverse engineering of the RVLink MV2402 roof unit’s internal API. The primary goal was to achieve direct, authenticated programmatic control by re-implementing key functionalities of the original webmgnt CGI interface using a custom Lua CGI script (skyweave.lua) running on the OpenWrt-based device. This involved analyzing C disassembly from Ghidra, mapping functionality to standard OpenWrt commands, and developing Lua handlers for status retrieval and network configuration, particularly for Wi-Fi client connections, Wi-Fi scanning, and system log access.
Key Topics & Discussion Points
Initial Project Context & Goals
- Objective: Achieve direct programmatic control over the RVLink MV2402 (192.168.10.254) via its internal API, bypassing the indoor unit (RV2458).
- MV2402 Independence: Largely achieved by disabling
wtpdandheart_beatservices, allowing standard OpenWrtnetifd/UCIto manage persistent Wi-Fi. - Key Endpoints for Re-implementation: Focus on replicating functionalities like
guide_config, Wi-Fi scan, system log retrieval, and Wi-Fi client connection (motorhome_config_set).
Lua CGI Script (skyweave.lua) Setup on MV2402
- Environment: MV2402 running OpenWrt (Chaos Calmer based) with
uhttpdas the web server. opkg.confFix: User had previously updated/etc/opkg.confto use Chaos Calmer 15.05 archives, restoring package management.- JSON Library: Initially used
rxi/json.lua. A problem arose withjson.encodefailing, which the user resolved by identifying a library mismatch or compatibility issue uhttpdConfiguration: Configured to run Lua CGI scripts on a high port (42069) to avoid conflict with the existingnginxon port 80.- Script Structure: Discussed and implemented a dispatcher/handler structure for
skyweave.luato manage multiple endpoints cleanly.- Helper functions for sending JSON responses and errors.
- A main dispatch logic to route requests to specific handler functions based on query parameters.
- Suggestion to modularize utility functions (e.g.,
conf_parsers.lua).
Re-implementing guide_config Endpoint
- Purpose: To retrieve a comprehensive status of network and Wi-Fi configurations.
- Method: Mapped fields from the original
guide_configJSON output (fromCOMFAST Webmgnt Endpoints - Sheet1.csv) touci getcommands for relevant paths in/etc/config/networkand/etc/config/wireless(referenced fromuci_show.txt). - C Disassembly (cgi_system-status_GET_wireless_config): Analyzed to understand how the original firmware populates the “radios” and “wifis” sections, including default values and logic for
htmodederivation. - Lua Implementation: Developed a Lua handler (
handlers.get_guide_config) to fetch UCI values and construct the JSON response.
Re-implementing Wi-Fi Scan Endpoint (wifi_scan)
- Purpose: To scan for available Wi-Fi networks.
- Original C Handler (mbox_get_wifi_scan): Analyzed disassembly; it uses the
iwinfolibrary to perform the scan and format results into blobmsg. The input is an “ifname” (e.g., “radio0”). - Lua Implementation (
handlers.get_wifi_scan):- Uses
io.popen("iwinfo <ifname> scan")to execute the command-lineiwinfotool. - Parses the textual output of
iwinfoto extract SSID, BSSID (address), quality, and encryption for each found AP. - The “Quality” value was noted to be
X/70in theiwinfoCLI output, and the handler was updated to calculate a percentage like the C code ((X * 100) / 70). - Added functionality to include
signal(dBm) andmode(e.g., “Master”) in the parsed output, enhancing the API beyond the user’s initial sample JSON. - The target JSON structure is
{"result_count": N, "results": [{...}], "errCode":0, ...}.
- Uses
Re-implementing System Log Endpoint (systemlog_get)
- Purpose: To retrieve system logs.
- Original C Handler (mbox_get_systemlog): Analyzed disassembly; it runs
logread > /tmp/systemlog, then reads this file and returns its content nested as{"systemlog": {"systemlog": "log data..."}}. - Initial Lua Implementation (
handlers.get_system_log):- Uses
io.popen("logread", "r")to get log content directly. - Returns the raw log string in the nested JSON structure.
- Uses
- Enhanced Parsed Log Endpoint (
handlers.get_system_log_parsed):- Goal: To parse individual log lines into a structured format similar to
ProcessedSystemLogResponsefrom the Python API, including a SHA256 hash for each raw log line. - SHA256 Hashing: Implemented a Lua helper function
M.calculate_sha256(input_string)usingio.popen("echo -n '...' | sha256sum")because Lua 5.1 lacks built-in SHA256. - Log Line Parsing: Implemented
M.parse_log_line(raw_line)to extract timestamp, facility/level, process name, PID, and message using Lua string patterns. It also attempts to handle kernel messages and reformat timestamps to an ISO-like string (YYYY-MM-DDTHH:MM:SS). - The final JSON structure is
{"parsed_logs": [{...}], "processing_summary": "...", "errCode":0, ...}.
- Goal: To parse individual log lines into a structured format similar to
Re-implementing Wi-Fi Connect Endpoint (motorhome_config_set)
- Purpose: To command the roof unit to connect to a specified Wi-Fi network (SSID, BSSID, encryption, key). This is a SET operation.
- Original C Handler (mbox_set_motorhome_config_set):
- Parses input blobmsg for “motorhome” table containing connection parameters.
- Performs numerous
set_uci_valueanddel_uci_valuecalls to configurewireless.wwan(for STA mode: SSID, BSSID, encryption, key, device, network, mode) andnetwork.wan(to usevif-sta0as ifname, proto DHCP, and clear static IP settings). - Calls
wirte_apply_change("/tmp/network_change", "workmode"). - Calls
config_action_set(5).
- Lua Implementation (
handlers.set_connect_wifi):- Accepts parameters:
ssid,bssid,encryption,key, anddevice(defaults to “radio0”). - Uses a series of
os.execute("uci set ...")andos.execute("uci delete ...")commands to configurewireless.wwanandnetwork.wansections. - Handles various encryption types (“none”, “psk2”, “psk”, “wep”, basic “wpa2”/“wpa”).
- Applies changes using
uci commit wireless,uci commit network,wifi reload, and/etc/init.d/network reload. This replaces the COMFAST-specific apply mechanism. - This handler will require POST request handling in
skyweave.lua.
- Accepts parameters:
Success
I was able to make this work by ultimately copying the original developer’s method of executing a
/etc/init.d/network restartallafter setting and committing all of the relevant UCI fields. This isn’t ideal in my opinion since it also brings down the LAN interface and thus communications between the Mikrotik router and the roof unit, but, it works.
Understanding Original Firmware’s Apply Mechanism
wirte_apply_change(filename, content): Simple C function that writescontenttofilename(e.g., “/tmp/network_change” gets “workmode”). This acts as a flag/signal.config_action_set(bitmask): Sets bits in a global variableDAT_0047dec0to flag which configurations (network, wireless, etc.) are “dirty”.config_action_apply(): Iterates a predefined list of configurations. If a configuration’s corresponding bit is set inDAT_0047dec0, it callsuci_config_commit(<config_name>, 0)for that configuration. It also callsduplicate_judge()if flag 4 is set.duplicate_judge(): Reads SSIDs fromwireless.@wifi-iface[0-15]. Its logic seems to check if any of the first 8 SSIDs are empty strings, then writes “1” (empty found) or “0” to a status file viawrite_status_files. It does not perform service reloads.apply_settings(): This C function is called bywebmgntafter other actions.- It calls
config_action_apply()to commit UCI changes. - It gets a string of committed configurations (e.g., “network wireless ”).
- It sets this string as an environment variable
CHANGED_CONFIGS. - It then
fork()s andexeclp()s the shell script/lib/webcfg/apply.sh.
- It calls
/lib/webcfg/apply.sh: This shell script is the final piece.- It reads the
$CHANGED_CONFIGSenvironment variable. - It iterates through the changed configurations (e.g., “network”, “wireless”).
- Based on the config name and sometimes the content of flag files (like
/tmp/network_change), it executes the appropriate service reload/restart commands (e.g.,wifi reload,wifi reload_legacy,/etc/init.d/network restartall,ubus call network reload,/etc/init.d/firewall restartall, etc.).
- It reads the
- Conclusion: The
webmgntCGI is self-contained in its apply mechanism (UCI commit + execution ofapply.sh), explaining why it still works even whenwtpdandheart_beatare disabled.
Fetching Live Network Information
- DNS Servers (
getdnsC function): Parses/tmp/resolv.conf.autoto findnameserverentries for a specific interface section.- Lua equivalent
M.get_live_dns_servers_from_resolv("wan")developed forconf_parsers.lua.
- Lua equivalent
- Default Gateway (
/proc/net/route): The default gateway IP can be parsed from the line where Destination and Mask are00000000and theGflag (0x0002) is set. The IP is in hex.- Lua equivalent
M.get_default_gateway_from_route()developed forconf_parsers.lua. - Corrected Lua syntax for bitwise AND (which is
bit.band()withlua-bitopor pure mathmath.fmod(num, 4) >= 2for checking bit 0x0002) as Lua 5.1 doesn’t have&operator.
- Lua equivalent
- Live IP Address (
get_net_ipC function): Usesioctlcalls (SIOCGIFADDR- 0x8915 for IP,SIOCGIFNETMASK- 0x891b for netmask) on a socket to get the interface’s IP/netmask.- Lua equivalent for live IP:
io.popen("ubus call network.interface.<ifname> status")and parse JSON, or parseifconfig <ifname>output.
- Lua equivalent for live IP:
Bash Deploy Script API Test Refinement
- The original test condition
[[ "${RESPONSE}" == *"status"* ]]was failing because the successful JSON output fromget_network_configuseserrCodeanderrMsg, not a top-levelstatuskey. - Changed to
[[ "${RESPONSE}" == *'"errCode":0'* ]]for more reliable success detection.
Decisions Made
- Proceed with Lua for the custom CGI API (
skyweave.lua) on the MV2402. - Utilize a dispatcher/handler pattern within
skyweave.luafor organization. - Modularize file parsing and utility functions into
conf_parsers.lua. - Use
io.popento interact with system utilities (uci,logread,iwinfo,sha256sum,ubus) for implementing API functionalities. - Replicate OpenWrt standard behaviors for network configuration, bypassing COMFAST-specific layers like
wirte_apply_changeas a direct trigger for Lua-initiated actions. - For bitwise operations in Lua 5.1 without
lua-bitop, use pure Lua mathematical equivalents where feasible (e.g., for checking a single bit like the Gateway flag). - The
set_connect_wifiLua handler will directly performuci set,uci commit,wifi reload, and/etc/init.d/network reloadto apply Wi-Fi client settings.
Key Takeaways/Conclusions
- The original
webmgntfirmware on the MV2402 uses a combination of C functions for UCI manipulation (uci_config_commit) and an external shell script (/lib/webcfg/apply.sh) executed viafork/execlpfor applying settings by reloading/restarting services. This explains its self-contained nature for applying changes. - Standard OpenWrt utilities (
uci,logread,iwinfo,ubus, service init scripts,wificommand) provide sufficient means to replicate the core functionalities of thewebmgntAPI in a custom Lua CGI script. - Disabling
wtpdandheart_beaton the MV2402 allows for direct and persistent configuration via UCI, which the new Lua API can leverage. - Careful parsing of C disassembly (from Ghidra) is invaluable for understanding the original firmware’s logic, identifying key UCI options, system calls, and the overall configuration flow.
- Developing for older Lua versions (like 5.1 on OpenWrt Chaos Calmer) may require finding alternatives or implementing workarounds for missing built-in functionalities (e.g., bitwise operators, advanced JSON handling, SHA256).