/* SPDX-License-Identifier: GPL-2.0-only */ /* * ubus.c - provides wifimngr UBUS commands. * * Copyright (C) 2024 Iopsys Software Solutions AB. All rights reserved. * Copyright (C) 2025 Genexis AB. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "wifimngr.h" #include "debug.h" #include "policy.c" extern void *libsta_ratings; int wifimngr_add_object(struct wifimngr *w, const char *objname, int (*add_methods)(struct wifimngr *w, struct ubus_object *o), struct ubus_object *object) { struct ubus_object_type *obj_type; struct ubus_object *obj; int ret; obj = object; memset(obj, 0, sizeof(*obj)); obj_type = calloc(1, sizeof(struct ubus_object_type)); if (!obj_type) return -ENOMEM; obj->name = strdup(objname); if (add_methods) add_methods(w, obj); obj_type->name = obj->name; obj_type->n_methods = obj->n_methods; obj_type->methods = obj->methods; obj->type = obj_type; ret = ubus_add_object(w->ubus_ctx, obj); if (ret) { wifimngr_dbg("Failed to add '%s' object\n", obj->name); free((void *)obj->methods); free((void *)obj->name); free(obj_type); } return ret; } #define MAX_RADIO_METHODS 16 static int add_radio_methods(struct wifimngr *w, struct ubus_object *radio_obj, const char *radioname) { struct wifimngr_device *wdev = wifimngr_lookup_wifi_device(w, radioname); struct ubus_method *radio_methods; enum wifi_band band; int n_methods = 0; char *phyname; if (!wdev) return -1; radio_methods = calloc(MAX_RADIO_METHODS, sizeof(struct ubus_method)); if (!radio_methods) return -ENOMEM; phyname = wdev->phy; band = wdev->band; UBUS_METHOD_ADD(radio_methods, n_methods, UBUS_METHOD_NOARG("help", wl_radio_help)); UBUS_METHOD_ADD(radio_methods, n_methods, UBUS_METHOD_NOARG("status", wl_radio_status)); UBUS_METHOD_ADD(radio_methods, n_methods, UBUS_METHOD_NOARG("stats", wl_radio_stats)); UBUS_METHOD_ADD(radio_methods, n_methods, UBUS_METHOD("get", wl_radio_get_param, radio_get_param_policy)); //UBUS_METHOD_ADD(radio_methods, n_methods, // UBUS_METHOD_NOARG("temperature", wl_temperature)); UBUS_METHOD_ADD(radio_methods, n_methods, UBUS_METHOD("scan", wl_scan, wl_scan_policy)); if (libwifi_supports(phyname, "wifi_scan_ex")) { UBUS_METHOD_ADD(radio_methods, n_methods, UBUS_METHOD("scan_ex", wl_scan_ex, wl_scan_ex_policy)); } UBUS_METHOD_ADD(radio_methods, n_methods, UBUS_METHOD("scanresults", wl_scanresults, wl_scanres_policy)); UBUS_METHOD_ADD(radio_methods, n_methods, UBUS_METHOD("autochannel", wl_autochannel, wl_acs_policy)); if (libwifi_supports(phyname, "wifi_start_cac")) { UBUS_METHOD_ADD(radio_methods, n_methods, UBUS_METHOD("start_cac", wl_start_cac, wl_cac_policy)); UBUS_METHOD_ADD(radio_methods, n_methods, UBUS_METHOD("stop_cac", wl_stop_cac, wl_cac_policy)); } if (libwifi_supports(phyname, "wifi_add_iface")) { UBUS_METHOD_ADD(radio_methods, n_methods, UBUS_METHOD("add_iface", wl_add_iface, add_iface_policy)); } if (libwifi_supports(phyname, "wifi_del_iface")) { UBUS_METHOD_ADD(radio_methods, n_methods, UBUS_METHOD("del_iface", wl_del_iface, del_iface_policy)); } if (libwifi_supports(phyname, "wifi_channels_info")) { UBUS_METHOD_ADD(radio_methods, n_methods, UBUS_METHOD_NOARG("channels_info", wl_channels_info)); } UBUS_METHOD_ADD(radio_methods, n_methods, UBUS_METHOD("channels", wl_channels, wl_dump_channels_policy)); if (libwifi_supports(phyname, "wifi_get_opclass_preferences")) { UBUS_METHOD_ADD(radio_methods, n_methods, UBUS_METHOD_NOARG("opclass_preferences", wl_opclass_preferences)); } if (libwifi_supports(phyname, "wifi_simulate_radar") && (band == BAND_5 || band == BAND_DUAL || band == BAND_UNKNOWN)) { UBUS_METHOD_ADD(radio_methods, n_methods, UBUS_METHOD("simulate_radar", wl_simulate_radar, wl_radar_policy)); } radio_obj->methods = radio_methods; radio_obj->n_methods = n_methods; return 0; } #define MAX_WIFI_METHODS 8 int add_wifi_methods(struct wifimngr *w, struct ubus_object *wifi_obj) { int n_methods = 0; struct ubus_method *wifi_methods; wifi_methods = calloc(MAX_WIFI_METHODS, sizeof(struct ubus_method)); if (!wifi_methods) return -ENOMEM; UBUS_METHOD_ADD(wifi_methods, n_methods, UBUS_METHOD_NOARG("status", wl_status)); UBUS_METHOD_ADD(wifi_methods, n_methods, UBUS_METHOD_NOARG("help", wl_help)); UBUS_METHOD_ADD(wifi_methods, n_methods, UBUS_METHOD("debug", wifi_set_debug, wifi_set_debug_policy)); wifi_obj->methods = wifi_methods; wifi_obj->n_methods = n_methods; return 0; } int wifimngr_lookup_object(struct wifimngr *w, const char *objname) { uint32_t id; int ret; ret = ubus_lookup_id(w->ubus_ctx, objname, &id); if (ret == UBUS_STATUS_OK) return id; return ret == UBUS_STATUS_OK ? id : -1; } int wifimngr_add_radio_object(struct wifimngr *w, struct wifimngr_device *wdev) { char *radioname = wdev->device; struct wifi_ubus_object *wobj; char objname[64] = {0}; uint32_t id; int ret; snprintf(objname, 63, "%s.%s", WIFI_RADIO_OBJECT, radioname); /* Already added */ if (ubus_lookup_id(w->ubus_ctx, objname, &id) == UBUS_STATUS_OK) { wdev->object_id = id; return 0; } if (w->num_wifi_device_obj == WIFI_DEV_MAX_NUM) { wifimngr_dbg("Cannot add more than %d wifi-device objects\n", w->num_wifi_device_obj); return -1; } wobj = calloc(1, sizeof(struct wifi_ubus_object)); if (!wobj) return -ENOMEM; wobj->priv = wdev; wobj->obj.name = strdup(objname); add_radio_methods(w, &wobj->obj, radioname); wobj->obj_type.name = wobj->obj.name; wobj->obj_type.n_methods = wobj->obj.n_methods; wobj->obj_type.methods = wobj->obj.methods; wobj->obj.type = &wobj->obj_type; ret = ubus_add_object(w->ubus_ctx, &wobj->obj); if (ret) { free((void *)wobj->obj.methods); free((void *)wobj->obj.name); free(wobj); return ret; } w->num_wifi_device_obj++; wifimngr_dbg("Added '%s' radio obj\n", wobj->obj.name); wdev->object_id = wobj->obj.id; list_add_tail(&wobj->list, &w->radiolist); return 0; } #define MAX_AP_METHODS 48 static int add_ap_methods(struct wifimngr *w, struct ubus_object *interface_obj, const char *ifname) { struct ubus_method *ap_methods; int n_methods = 0; ap_methods = calloc(MAX_AP_METHODS, sizeof(struct ubus_method)); if (!ap_methods) return -ENOMEM; UBUS_METHOD_ADD(ap_methods, n_methods, UBUS_METHOD_NOARG("help", wl_ap_help)); UBUS_METHOD_ADD(ap_methods, n_methods, UBUS_METHOD_NOARG("status", wl_ap_status)); UBUS_METHOD_ADD(ap_methods, n_methods, UBUS_METHOD_NOARG("stats", wl_ap_stats)); UBUS_METHOD_ADD(ap_methods, n_methods, UBUS_METHOD_NOARG("assoclist", wl_assoclist)); UBUS_METHOD_ADD(ap_methods, n_methods, UBUS_METHOD("stations", wl_stations, stainfo_policy)); if (libsta_ratings) { UBUS_METHOD_ADD(ap_methods, n_methods, UBUS_METHOD("sta_ratings", wl_sta_ratings, stainfo_policy)); } if (libwifi_supports(ifname, "wifi_get_blocked_stas")) { UBUS_METHOD_ADD(ap_methods, n_methods, UBUS_METHOD_NOARG("blocked_stas", wl_blocked_stas)); } if (libwifi_supports(ifname, "wifi_block_sta")) { UBUS_METHOD_ADD(ap_methods, n_methods, UBUS_METHOD("block_sta", wl_block_sta, block_station_policy)); } if (libwifi_supports(ifname, "wifi_disconnect_sta")) { UBUS_METHOD_ADD(ap_methods, n_methods, UBUS_METHOD("disconnect", sta_disconnect, sta_disconnect_policy)); } if (libwifi_supports(ifname, "wifi_probe_sta")) { UBUS_METHOD_ADD(ap_methods, n_methods, UBUS_METHOD("probe_sta", sta_probe, sta_monitor_policy)); } if (libwifi_supports(ifname, "wifi_monitor_sta")) { UBUS_METHOD_ADD(ap_methods, n_methods, UBUS_METHOD("monitor_add", sta_monitor_add, sta_monitor_policy)); UBUS_METHOD_ADD(ap_methods, n_methods, UBUS_METHOD("monitor_del", sta_monitor_del, sta_monitor_policy)); UBUS_METHOD_ADD(ap_methods, n_methods, UBUS_METHOD("monitor_get", sta_monitor_get, sta_monitor_policy)); } if (libwifi_supports(ifname, "wifi_subscribe_frame")) UBUS_METHOD_ADD(ap_methods, n_methods, UBUS_METHOD("subscribe_frame", subscribe_frame, subscribe_frame_policy)); if (libwifi_supports(ifname, "wifi_unsubscribe_frame")) UBUS_METHOD_ADD(ap_methods, n_methods, UBUS_METHOD("unsubscribe_frame", unsubscribe_frame, subscribe_frame_policy)); if (libwifi_supports(ifname, "wifi_add_neighbor")) UBUS_METHOD_ADD(ap_methods, n_methods, UBUS_METHOD("add_neighbor", nbr_add, nbr_add_policy)); if (libwifi_supports(ifname, "wifi_del_neighbor")) UBUS_METHOD_ADD(ap_methods, n_methods, UBUS_METHOD("del_neighbor", nbr_del, nbr_del_policy)); if (libwifi_supports(ifname, "wifi_get_neighbor_list")) UBUS_METHOD_ADD(ap_methods, n_methods, UBUS_METHOD("list_neighbor", nbr_list, nbr_list_policy)); if (libwifi_supports(ifname, "wifi_req_beacon_report")) UBUS_METHOD_ADD(ap_methods, n_methods, UBUS_METHOD("request_neighbor", nbr_request, nbr_req_policy)); if (libwifi_supports(ifname, "wifi_req_btm")) UBUS_METHOD_ADD(ap_methods, n_methods, UBUS_METHOD("request_btm", btm_request, btmreq_policy)); if (libwifi_supports(ifname, "wifi_restrict_sta")) UBUS_METHOD_ADD(ap_methods, n_methods, UBUS_METHOD("assoc_control", assoc_control, assoc_cntrl_policy)); if (libwifi_supports(ifname, "wifi_add_vendor_ie")) UBUS_METHOD_ADD(ap_methods, n_methods, UBUS_METHOD("add_vendor_ie", vsie_add, vsie_policy)); if (libwifi_supports(ifname, "wifi_del_vendor_ie")) UBUS_METHOD_ADD(ap_methods, n_methods, UBUS_METHOD("del_vendor_ie", vsie_del, vsie_policy)); if (libwifi_supports(ifname, "wifi_get_beacon_ies")) UBUS_METHOD_ADD(ap_methods, n_methods, UBUS_METHOD_NOARG("dump_beacon", wl_ap_dump_beacon)); if (libwifi_supports(ifname, "wifi_chan_switch")) UBUS_METHOD_ADD(ap_methods, n_methods, UBUS_METHOD("chan_switch", ap_chan_switch, chan_switch_policy)); if (libwifi_supports(ifname, "wifi_link_measure")) UBUS_METHOD_ADD(ap_methods, n_methods, UBUS_METHOD("measure_link", ap_measure_link, linkmeas_policy)); if (libwifi_supports(ifname, "wifi_mbo_disallow_assoc")) UBUS_METHOD_ADD(ap_methods, n_methods, UBUS_METHOD("mbo_disallow_assoc", ap_mbo_disallow_assoc, mbo_disallow_assoc_policy)); if (libwifi_supports(ifname, "wifi_ap_set_state")) { UBUS_METHOD_ADD(ap_methods, n_methods, UBUS_METHOD_NOARG("up", ap_bss_up)); UBUS_METHOD_ADD(ap_methods, n_methods, UBUS_METHOD_NOARG("down", ap_bss_down)); } if (libwifi_supports(ifname, "wifi_send_action_frame")) { UBUS_METHOD_ADD(ap_methods, n_methods, UBUS_METHOD("send_action", ap_send_action, send_action_policy)); } if (libwifi_supports(ifname, "wifi_ap_set_qos_map")) { UBUS_METHOD_ADD(ap_methods, n_methods, UBUS_METHOD("set_qos_map", set_qos_map, ap_set_qos_map_policy)); } if (libwifi_supports(ifname, "wifi_ap_send_qos_map_conf")) { UBUS_METHOD_ADD(ap_methods, n_methods, UBUS_METHOD("send_qos_map_conf", send_qos_map_conf, ap_send_qos_map_conf_policy)); } interface_obj->methods = ap_methods; interface_obj->n_methods = n_methods; return 0; } #define MAX_STA_METHODS 8 static int add_sta_methods(struct wifimngr *w, struct ubus_object *interface_obj, const char *ifname) { int n_methods = 0; struct ubus_method *sta_methods; sta_methods = calloc(MAX_STA_METHODS, sizeof(struct ubus_method)); if (!sta_methods) return -ENOMEM; UBUS_METHOD_ADD(sta_methods, n_methods, UBUS_METHOD_NOARG("status", wl_sta_status)); UBUS_METHOD_ADD(sta_methods, n_methods, UBUS_METHOD_NOARG("stats", wl_sta_stats)); if (libwifi_supports(ifname, "wifi_sta_disconnect_ap")) { UBUS_METHOD_ADD(sta_methods, n_methods, UBUS_METHOD("disconnect", wl_sta_disconnect_ap, ap_disconnect_policy)); } if (libwifi_supports(ifname, "wifi_set_4addr")) { UBUS_METHOD_ADD(sta_methods, n_methods, UBUS_METHOD("4addr", wl_sta_4addr, sta_4addr_policy)); } if (libwifi_supports(ifname, "wifi_send_action_frame")) { UBUS_METHOD_ADD(sta_methods, n_methods, UBUS_METHOD("send_action", ap_send_action, send_action_policy)); } if (libwifi_supports(ifname, "wifi_dpp_listen")) { UBUS_METHOD_ADD(sta_methods, n_methods, UBUS_METHOD("dpp_listen", wl_sta_dpp_listen, dpp_listen_policy)); } if (libwifi_supports(ifname, "wifi_dpp_stop_listen")) { UBUS_METHOD_ADD(sta_methods, n_methods, UBUS_METHOD_NOARG("dpp_stop_listen", wl_sta_dpp_stop_listen)); } interface_obj->methods = sta_methods; interface_obj->n_methods = n_methods; return 0; } int wifimngr_add_interface_object(struct wifimngr *w, struct wifimngr_iface *iface) { struct wifi_ubus_object *wobj; char objname[64] = {0}; uint32_t id; int ret; if (iface->mode == WIFI_MODE_AP) { snprintf(objname, 63, "%s.%s", WIFI_AP_OBJECT, iface->iface); } else if (iface->mode == WIFI_MODE_STA) { snprintf(objname, 63, "%s.%s", WIFI_BSTA_OBJECT, iface->iface); } else { /* unhandled wifi mode */ return -EINVAL; } /* Already added */ if (ubus_lookup_id(w->ubus_ctx, objname, &id) == UBUS_STATUS_OK) { iface->object_id = id; return 0; } wobj = calloc(1, sizeof(struct wifi_ubus_object)); if (!wobj) return -ENOMEM; wobj->priv = iface; if (iface->mode == WIFI_MODE_AP) add_ap_methods(w, &wobj->obj, iface->iface); else add_sta_methods(w, &wobj->obj, iface->iface); wobj->obj.name = strdup(objname); wobj->obj_type.name = wobj->obj.name; wobj->obj_type.n_methods = wobj->obj.n_methods; wobj->obj_type.methods = wobj->obj.methods; wobj->obj.type = &wobj->obj_type; ret = ubus_add_object(w->ubus_ctx, &wobj->obj); if (!ret) { iface->object_id = wobj->obj.id; list_add_tail(&wobj->list, &w->iflist); } else { free((void *)wobj->obj.methods); free((void *)wobj->obj.name); free(wobj); } return ret; } #define MAX_APMLD_METHODS 16 static int add_apmld_methods(struct wifimngr *w, struct ubus_object *interface_obj, const char *ifname) { struct ubus_method *apmld_methods; int n_methods = 0; apmld_methods = calloc(MAX_APMLD_METHODS, sizeof(struct ubus_method)); if (!apmld_methods) return -ENOMEM; UBUS_METHOD_ADD(apmld_methods, n_methods, UBUS_METHOD_NOARG("help", wl_apmld_help)); UBUS_METHOD_ADD(apmld_methods, n_methods, UBUS_METHOD_NOARG("status", wl_apmld_status)); UBUS_METHOD_ADD(apmld_methods, n_methods, UBUS_METHOD_NOARG("stats", wl_apmld_stats)); UBUS_METHOD_ADD(apmld_methods, n_methods, UBUS_METHOD_NOARG("assoclist", wl_apmld_assoclist)); UBUS_METHOD_ADD(apmld_methods, n_methods, UBUS_METHOD("stations", wl_apmld_stations, stainfo_policy)); if (libsta_ratings) { UBUS_METHOD_ADD(apmld_methods, n_methods, UBUS_METHOD("sta_ratings", wl_apmld_sta_ratings, stainfo_policy)); } if (libwifi_supports(ifname, "wifi_disconnect_sta")) { UBUS_METHOD_ADD(apmld_methods, n_methods, UBUS_METHOD("disconnect", wl_apmld_disconnect_sta, sta_disconnect_policy)); } if (libwifi_supports(ifname, "wifi_block_sta")) { UBUS_METHOD_ADD(apmld_methods, n_methods, UBUS_METHOD("block_sta", wl_apmld_block_sta, apmld_block_station_policy)); } if (libwifi_supports(ifname, "wifi_get_blocked_stas")) { UBUS_METHOD_ADD(apmld_methods, n_methods, UBUS_METHOD("blocked_stas", wl_apmld_blocked_stas, apmld_blocked_maclist_policy)); } if (libwifi_supports(ifname, "wifi_get_beacon_ies")) { UBUS_METHOD_ADD(apmld_methods, n_methods, UBUS_METHOD("dump_beacon", wl_apmld_dump_beacon, dump_beacon_policy)); } interface_obj->methods = apmld_methods; interface_obj->n_methods = n_methods; return 0; } #define MAX_MLDSTA_METHODS 8 static int add_mldsta_methods(struct wifimngr *w, struct ubus_object *interface_obj, const char *ifname) { struct ubus_method *mldsta_methods; int n_methods = 0; mldsta_methods = calloc(MAX_MLDSTA_METHODS, sizeof(struct ubus_method)); if (!mldsta_methods) return -ENOMEM; UBUS_METHOD_ADD(mldsta_methods, n_methods, UBUS_METHOD_NOARG("status", wl_mldsta_status)); UBUS_METHOD_ADD(mldsta_methods, n_methods, UBUS_METHOD_NOARG("stats", wl_mldsta_stats)); if (libwifi_supports(ifname, "wifi_sta_disconnect_ap")) { UBUS_METHOD_ADD(mldsta_methods, n_methods, UBUS_METHOD("disconnect", wl_mldsta_disconnect_ap, ap_disconnect_policy)); } if (libwifi_supports(ifname, "wifi_set_4addr")) { UBUS_METHOD_ADD(mldsta_methods, n_methods, UBUS_METHOD("4addr", wl_sta_4addr, sta_4addr_policy)); } interface_obj->methods = mldsta_methods; interface_obj->n_methods = n_methods; return 0; } int wifimngr_add_mld_interface_object(struct wifimngr *w, struct wifimngr_mld *mldif) { struct wifi_ubus_object *wobj; char objname[64] = {0}; uint32_t id; int ret; if (mldif->mode == WIFI_MODE_AP) snprintf(objname, 63, "%s.%s", WIFI_APMLD_OBJECT, mldif->ifname); else if (mldif->mode == WIFI_MODE_STA) snprintf(objname, 63, "%s.%s", WIFI_BSTAMLD_OBJECT, mldif->ifname); else return -EINVAL; /* Already added */ if (ubus_lookup_id(w->ubus_ctx, objname, &id) == UBUS_STATUS_OK) { mldif->object_id = id; return 0; } wobj = calloc(1, sizeof(struct wifi_ubus_object)); if (!wobj) return -ENOMEM; wobj->priv = mldif; if (mldif->mode == WIFI_MODE_AP) add_apmld_methods(w, &wobj->obj, mldif->ifname); else add_mldsta_methods(w, &wobj->obj, mldif->ifname); wobj->obj.name = strdup(objname); wobj->obj_type.name = wobj->obj.name; wobj->obj_type.n_methods = wobj->obj.n_methods; wobj->obj_type.methods = wobj->obj.methods; wobj->obj.type = &wobj->obj_type; ret = ubus_add_object(w->ubus_ctx, &wobj->obj); if (!ret) { mldif->object_id = wobj->obj.id; list_add_tail(&wobj->list, &w->iflist); } else { free((void *)wobj->obj.methods); free((void *)wobj->obj.name); free(wobj); } return ret; } void wifimngr_del_object(struct wifimngr *w, struct ubus_object *obj, bool remove_from_ubus) { /* Remove from ubus before freeing memory to avoid use-after-free. * Skip removal only during final cleanup when ubus_free() will handle it. */ if (remove_from_ubus && w->ubus_ctx && obj->id) ubus_remove_object(w->ubus_ctx, obj); if (obj->methods) free((void *)obj->methods); if (obj->name) free((void *)obj->name); } int wifimngr_add_objects(struct wifimngr *w) { int ret; ret = wifimngr_add_object(w, WIFI_OBJECT, add_wifi_methods, &w->wifi_obj); if (ret) { wifimngr_err("Failed to add '%s' ubus object: %s\n", WIFI_OBJECT, ubus_strerror(ret)); goto out_exit; } ret = wifimngr_add_object(w, WIFI_WPS_OBJECT, add_wps_methods, &w->wifi_wps_obj); if (ret) { wifimngr_err("Failed to add '%s' ubus object: %s\n", WIFI_WPS_OBJECT, ubus_strerror(ret)); goto out_exit; } wifimngr_reconfig(w); return 0; out_exit: return -1; }