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 wtpd and heart_beat services, allowing standard OpenWrt netifd/UCI to 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 uhttpd as the web server.
  • opkg.conf Fix: User had previously updated /etc/opkg.conf to use Chaos Calmer 15.05 archives, restoring package management.
  • JSON Library: Initially used rxi/json.lua. A problem arose with json.encode failing, which the user resolved by identifying a library mismatch or compatibility issue
  • uhttpd Configuration: Configured to run Lua CGI scripts on a high port (42069) to avoid conflict with the existing nginx on port 80.
  • Script Structure: Discussed and implemented a dispatcher/handler structure for skyweave.lua to 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_config JSON output (from COMFAST Webmgnt Endpoints - Sheet1.csv) to uci get commands for relevant paths in /etc/config/network and /etc/config/wireless (referenced from uci_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 htmode derivation.
  • 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 iwinfo library 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-line iwinfo tool.
    • Parses the textual output of iwinfo to extract SSID, BSSID (address), quality, and encryption for each found AP.
    • The “Quality” value was noted to be X/70 in the iwinfo CLI output, and the handler was updated to calculate a percentage like the C code ((X * 100) / 70).
    • Added functionality to include signal (dBm) and mode (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, ...}.

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.
  • Enhanced Parsed Log Endpoint (handlers.get_system_log_parsed):
    • Goal: To parse individual log lines into a structured format similar to ProcessedSystemLogResponse from the Python API, including a SHA256 hash for each raw log line.
    • SHA256 Hashing: Implemented a Lua helper function M.calculate_sha256(input_string) using io.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, ...}.

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_value and del_uci_value calls to configure wireless.wwan (for STA mode: SSID, BSSID, encryption, key, device, network, mode) and network.wan (to use vif-sta0 as 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, and device (defaults to “radio0”).
    • Uses a series of os.execute("uci set ...") and os.execute("uci delete ...") commands to configure wireless.wwan and network.wan sections.
    • 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.

Success

I was able to make this work by ultimately copying the original developer’s method of executing a /etc/init.d/network restartall after 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 writes content to filename (e.g., “/tmp/network_change” gets “workmode”). This acts as a flag/signal.
  • config_action_set(bitmask): Sets bits in a global variable DAT_0047dec0 to 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 in DAT_0047dec0, it calls uci_config_commit(<config_name>, 0) for that configuration. It also calls duplicate_judge() if flag 4 is set.
  • duplicate_judge(): Reads SSIDs from wireless.@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 via write_status_files. It does not perform service reloads.
  • apply_settings(): This C function is called by webmgnt after other actions.
    1. It calls config_action_apply() to commit UCI changes.
    2. It gets a string of committed configurations (e.g., “network wireless ”).
    3. It sets this string as an environment variable CHANGED_CONFIGS.
    4. It then fork()s and execlp()s the shell script /lib/webcfg/apply.sh.
  • /lib/webcfg/apply.sh: This shell script is the final piece.
    1. It reads the $CHANGED_CONFIGS environment variable.
    2. It iterates through the changed configurations (e.g., “network”, “wireless”).
    3. 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.).
  • Conclusion: The webmgnt CGI is self-contained in its apply mechanism (UCI commit + execution of apply.sh), explaining why it still works even when wtpd and heart_beat are disabled.

Fetching Live Network Information

  • DNS Servers (getdns C function): Parses /tmp/resolv.conf.auto to find nameserver entries for a specific interface section.
    • Lua equivalent M.get_live_dns_servers_from_resolv("wan") developed for conf_parsers.lua.
  • Default Gateway (/proc/net/route): The default gateway IP can be parsed from the line where Destination and Mask are 00000000 and the G flag (0x0002) is set. The IP is in hex.
    • Lua equivalent M.get_default_gateway_from_route() developed for conf_parsers.lua.
    • Corrected Lua syntax for bitwise AND (which is bit.band() with lua-bitop or pure math math.fmod(num, 4) >= 2 for checking bit 0x0002) as Lua 5.1 doesn’t have & operator.
  • Live IP Address (get_net_ip C function): Uses ioctl calls (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 parse ifconfig <ifname> output.

Bash Deploy Script API Test Refinement

  • The original test condition [[ "${RESPONSE}" == *"status"* ]] was failing because the successful JSON output from get_network_config uses errCode and errMsg, not a top-level status key.
  • 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.lua for organization.
  • Modularize file parsing and utility functions into conf_parsers.lua.
  • Use io.popen to 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_change as 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_wifi Lua handler will directly perform uci set, uci commit, wifi reload, and /etc/init.d/network reload to apply Wi-Fi client settings.

Key Takeaways/Conclusions

  • The original webmgnt firmware 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 via fork/execlp for 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, wifi command) provide sufficient means to replicate the core functionalities of the webmgnt API in a custom Lua CGI script.
  • Disabling wtpd and heart_beat on 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).

Wesley Ray · blog · git · resume