--- /dev/null
+/*
+ * Network Configuration Module
+ *
+ * Copyright (c) 2000 - 2012 Samsung Electronics Co., Ltd. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include <glib.h>
+
+#include "netsupplicant.h"
+#include "log.h"
+#include "util.h"
+#include "wifi-netlink-scan.h"
+#include <netlink/genl/genl.h>
+#include <netlink/genl/family.h>
+#include <netlink/genl/ctrl.h>
+#include <netlink/msg.h>
+#include <netlink/attr.h>
+#include <netlink/netlink.h>
+#include <ctype.h>
+
+static GSList *bss_info_list = NULL;
+static guint scan_timer = 0;
+static unsigned char samsung_oui[3] = {0x00, 0x16, 0x32};
+
+static gboolean __netconfig_scan_timeout(gpointer data)
+{
+ __netconfig_notify_netlink_scan_done();
+
+ return FALSE;
+}
+
+static void __netconfig_start_scan_timer(void)
+{
+ if (scan_timer == 0) {
+ netconfig_start_timer_seconds(5, __netconfig_scan_timeout, NULL, &scan_timer);
+ INFO("Get scan data timer started: %d", scan_timer);
+ }
+}
+
+static void __netconfig_stop_scan_timer(void)
+{
+ netconfig_stop_timer(&scan_timer);
+ INFO("Get scan data timer stopped: %d", scan_timer);
+}
+
+void __netconfig_notify_netlink_scan_done(void)
+{
+ GVariantBuilder *builder = NULL;
+ GSList* list = NULL;
+ const char *prop_ssid = "ssid";
+ const char *prop_bssid = "bssid";
+ const char *prop_freq = "freq";
+ const char *prop_rssi = "rssi";
+ const char *prop_vsie = "vsie";
+
+ builder = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
+ for (list = bss_info_list; list != NULL; list = list->next) {
+ struct bss_scan_info_t *bss_info = (struct bss_scan_info_t *)list->data;
+
+ if (bss_info) {
+ char *bssid = (char *)bss_info->bssid;
+ char *ssid = (char *)bss_info->ssid;
+ char *vsie = (char *)bss_info->vsie;
+ int freq = (int)bss_info->freq;
+ int signal = (int)bss_info->signal;
+
+ g_variant_builder_add(builder, "{sv}", prop_ssid, g_variant_new_string(ssid));
+ g_variant_builder_add(builder, "{sv}", prop_bssid, g_variant_new_string(bssid));
+ g_variant_builder_add(builder, "{sv}", prop_freq, g_variant_new_int32(freq));
+ g_variant_builder_add(builder, "{sv}", prop_rssi, g_variant_new_int32(signal));
+ g_variant_builder_add(builder, "{sv}", prop_vsie, g_variant_new_string(vsie));
+ }
+ }
+
+ wifi_emit_netlink_scan_completed((Wifi *)get_wifi_object(), g_variant_builder_end(builder));
+ g_variant_builder_unref(builder);
+
+ if (bss_info_list != NULL)
+ g_slist_free_full(bss_info_list, g_free);
+
+ bss_info_list = NULL;
+ __netconfig_stop_scan_timer();
+ INFO("NetlinkScanCompleted");
+
+ return;
+}
+
+static int ack_handler(struct nl_msg *msg, void *user_data)
+{
+ int *err = user_data;
+ *err = 0;
+ return NL_STOP;
+}
+
+static int finish_handler(struct nl_msg *msg, void *user_data)
+{
+ int *ret = user_data;
+ *ret = 0;
+ return NL_SKIP;
+}
+
+static int error_handler(struct sockaddr_nl *nla, struct nlmsgerr *err,
+ void *user_data)
+{
+ int *ret = user_data;
+ *ret = err->error;
+ return NL_SKIP;
+}
+
+static int no_seq_check(struct nl_msg *msg, void *user_data)
+{
+ DBG("");
+ return NL_OK;
+}
+
+static int __netconfig_family_handler(struct nl_msg *msg, void *user_data)
+{
+ /** Callback for NL_CB_VALID in multicast group */
+ struct netconfig_netlink_scan_handler_args *grp = user_data;
+ struct nlattr *tb[CTRL_ATTR_MAX + 1];
+ struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
+ struct nlattr *mc_grp;
+ int rem_mc_grp;
+
+ nla_parse(tb, CTRL_ATTR_MAX, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), NULL);
+
+ if (!tb[CTRL_ATTR_MCAST_GROUPS])
+ return NL_SKIP;
+
+ nla_for_each_nested(mc_grp, tb[CTRL_ATTR_MCAST_GROUPS], rem_mc_grp) {
+ struct nlattr *tb_mc_grp[CTRL_ATTR_MCAST_GRP_MAX + 1];
+
+ nla_parse(tb_mc_grp, CTRL_ATTR_MCAST_GRP_MAX, nla_data(mc_grp), nla_len(mc_grp), NULL);
+
+ if (!tb_mc_grp[CTRL_ATTR_MCAST_GRP_NAME] || !tb_mc_grp[CTRL_ATTR_MCAST_GRP_ID])
+ continue;
+ if (strncmp(nla_data(tb_mc_grp[CTRL_ATTR_MCAST_GRP_NAME]), grp->group,
+ nla_len(tb_mc_grp[CTRL_ATTR_MCAST_GRP_NAME])))
+ continue;
+
+ grp->id = nla_get_u32(tb_mc_grp[CTRL_ATTR_MCAST_GRP_ID]);
+ break;
+ }
+
+ return NL_SKIP;
+}
+
+static int __netconfig_get_multicast_id(struct nl_sock *socket, const char *family, const char *group)
+{
+ struct nl_msg *msg = NULL;
+ struct nl_cb *cb = NULL;
+ int ret, ctrl_id;
+ struct netconfig_netlink_scan_handler_args grp = { .group = group, .id = -ENOENT, };
+
+ msg = nlmsg_alloc();
+ if (!msg)
+ return -ENOMEM;
+
+ cb = nl_cb_alloc(NL_CB_DEFAULT);
+ if (!cb) {
+ ret = -ENOMEM;
+ goto cb_fail;
+ }
+
+ ctrl_id = genl_ctrl_resolve(socket, "nlctrl");
+
+ genlmsg_put(msg, 0, 0, ctrl_id, 0, 0, CTRL_CMD_GETFAMILY, 0);
+
+ ret = -ENOBUFS;
+ NLA_PUT_STRING(msg, CTRL_ATTR_FAMILY_NAME, family);
+
+ ret = nl_send_auto_complete(socket, msg);
+ if (ret < 0)
+ goto out;
+
+ ret = 1;
+
+ nl_cb_err(cb, NL_CB_CUSTOM, error_handler, &ret);
+ nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, ack_handler, &ret);
+ nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, __netconfig_family_handler, &grp);
+
+ while (ret > 0)
+ nl_recvmsgs(socket, cb);
+
+ if (ret == 0)
+ ret = grp.id;
+
+nla_put_failure:
+out:
+ nl_cb_put(cb);
+cb_fail:
+ nlmsg_free(msg);
+ return ret;
+}
+
+static void __netconfig_macaddress_str(char *bssid, unsigned char *user_data)
+{
+ int i;
+
+ for (i = 0; i < 6; i++) {
+ if (i == 0) {
+ snprintf(bssid, 3, "%02x", user_data[i]);
+ bssid += 2;
+ } else {
+ snprintf(bssid, 4, ":%02x", user_data[i]);
+ bssid += 3;
+ }
+ }
+}
+
+static void __netconfig_get_vsie(unsigned char *bss_element, int length, char **dst)
+{
+ int i = 0;
+ uint8_t len = 0;
+ gboolean vsie_found = FALSE;
+
+ if (length < 3) {
+ DBG("Vendor specific data not available");
+ return;
+ }
+
+ /** Check for vendor specific information element */
+ for (i = 0; i < length; i++) {
+ if (bss_element[i] == 221) {
+ len = bss_element[i+1];
+ vsie_found = TRUE;
+ goto out;
+ }
+ }
+out:
+ if (vsie_found && memcmp(bss_element+i+2, samsung_oui, 3) == 0) {
+ DBG("Vendor Specific IE found, len: %d", len);
+ *dst = g_try_malloc0(2*(len+2) + 1);
+ if (*dst == NULL) {
+ DBG("Failed to allocate memory");
+ return;
+ }
+ char *buf = (*dst);
+ int j = 0;
+
+ for (j = i; j <= (i + len + 1); j++) {
+ snprintf(buf, 3, "%02x", bss_element[j]);
+ buf += 2;
+ }
+
+ vsie_found = FALSE;
+ }
+}
+
+static void __netconfig_found_ap(unsigned char *bss_element, int length, char *str)
+{
+ uint8_t len;
+ uint8_t *data;
+ int i;
+
+ while (length >= 2 && length >= bss_element[1]) {
+ if (bss_element[0] == 0 && bss_element[1] <= 32) {
+ len = bss_element[1];
+ data = bss_element + 2;
+ for (i = 0; i < len; i++) {
+ if (isprint(data[i]) && data[i] != ' ' && data[i] != '\\')
+ snprintf(&str[i], 2, "%c", data[i]);
+ else if (data[i] == ' ' && (i != 0 && i != len -1))
+ snprintf(&str[i], 2, "%c", ' ');
+ else
+ snprintf(&str[i], 3, "%.2x", data[i]);
+ }
+ break;
+ }
+ length -= bss_element[1] + 2;
+ bss_element += bss_element[1] + 2;
+ }
+}
+
+static int __netconfig_netlink_scan_cb(struct nl_msg *msg, void *user_data)
+{
+ /** Called by the kernel with a dump of the successful scan's data. Called for each SSID. */
+ struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
+ char bssid[NETCONFIG_BSSID_LEN+1];
+ char ssid[NETCONFIG_SSID_LEN+1] = {0, };
+ char *vsie = NULL;
+ struct nlattr *tb[NL80211_ATTR_MAX + 1];
+ struct nlattr *bss[NL80211_BSS_MAX + 1];
+ struct nla_policy bss_policy[NL80211_BSS_MAX + 1] = {
+ [NL80211_BSS_FREQUENCY] = {.type = NLA_U32},
+ [NL80211_BSS_BSSID] = { },
+ [NL80211_BSS_INFORMATION_ELEMENTS] = { },
+ [NL80211_BSS_SIGNAL_MBM] = {.type = NLA_U32},
+ };
+
+ /** Parse nl message and check error. */
+ nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), NULL);
+ if (!tb[NL80211_ATTR_BSS]) {
+ DBG("BSS info not available");
+ return NL_SKIP;
+ }
+ if (nla_parse_nested(bss, NL80211_BSS_MAX, tb[NL80211_ATTR_BSS], bss_policy)) {
+ DBG("Failed to parse nested attributes");
+ return NL_SKIP;
+ }
+ if ((!bss[NL80211_BSS_BSSID]) || (!bss[NL80211_BSS_INFORMATION_ELEMENTS]))
+ return NL_SKIP;
+
+ /** Extract BSSID and AP info. */
+ __netconfig_macaddress_str(bssid, nla_data(bss[NL80211_BSS_BSSID]));
+ __netconfig_found_ap(nla_data(bss[NL80211_BSS_INFORMATION_ELEMENTS]), nla_len(bss[NL80211_BSS_INFORMATION_ELEMENTS]), ssid);
+ __netconfig_get_vsie(nla_data(bss[NL80211_BSS_INFORMATION_ELEMENTS]), nla_len(bss[NL80211_BSS_INFORMATION_ELEMENTS]), &vsie);
+
+ /** Create AP info list. */
+ if (ssid[0] != '\0') {
+ struct bss_scan_info_t *bss_info;
+ int signal;
+
+ bss_info = g_try_new0(struct bss_scan_info_t, 1);
+ if (bss_info == NULL)
+ return NL_SKIP;
+
+ g_strlcpy(bss_info->bssid, bssid, strlen(bssid)+1);
+ g_strlcpy(bss_info->ssid, ssid, strlen(ssid)+1);
+ if (vsie) {
+ g_strlcpy(bss_info->vsie, vsie, strlen(vsie)+1);
+ g_free(vsie);
+ }
+ bss_info->freq = nla_get_u32(bss[NL80211_BSS_FREQUENCY]);
+
+ if (bss[NL80211_BSS_SIGNAL_MBM]) {
+ signal = nla_get_u32(bss[NL80211_BSS_SIGNAL_MBM]);
+ signal /= 100; /** mBm to dBm */
+ bss_info->signal = signal;
+ }
+ DBG("%s %d %d %s [vsie: %s]", bss_info->bssid, bss_info->freq, bss_info->signal, bss_info->ssid, bss_info->vsie);
+
+ if (bss_info->ssid[0] == '\0')
+ g_free(bss_info);
+ else
+ bss_info_list = g_slist_append(bss_info_list, bss_info);
+
+ if (scan_timer == 0) {
+ DBG("Start scan timer");
+ __netconfig_start_scan_timer();
+ }
+ }
+
+ return NL_SKIP;
+}
+
+static int __netconfig_netlink_scan_reply(struct nl_msg *msg, void *user_data)
+{
+ /** Called by the kernel when the scan is done or has been aborted. */
+ struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
+ struct netconfig_netlink_scan_results *results = user_data;
+
+ if (gnlh->cmd == NL80211_CMD_NEW_SCAN_RESULTS) {
+ DBG("Received NL80211_CMD_NEW_SCAN_RESULTS in reply");
+ results->done = 1;
+ results->aborted = 0;
+ } else if (gnlh->cmd == NL80211_CMD_SCAN_ABORTED) {
+ DBG("Received NL80211_CMD_SCAN_ABORTED in reply");
+ results->done = 1;
+ results->aborted = 1;
+ }
+
+ return NL_SKIP;
+}
+
+static int __netconfig_request_netlink_scan(struct nl_sock *socket, int if_index, int id)
+{
+ struct netconfig_netlink_scan_results results = { .done = 0, .aborted = 0 };
+ struct nl_msg *msg = NULL;
+ struct nl_cb *cb = NULL;
+ struct nl_msg *ssids = NULL;
+ int err = 0;
+ int ret = 0;
+ int mcid = __netconfig_get_multicast_id(socket, "nl80211", "scan");
+ nl_socket_add_membership(socket, mcid);
+
+ msg = nlmsg_alloc();
+ if (!msg) {
+ DBG("Failed to allocate msg");
+ return -ENOMEM;
+ }
+ ssids = nlmsg_alloc();
+ if (!ssids) {
+ DBG("Failed to allocate ssids");
+ nlmsg_free(msg);
+ return -ENOMEM;
+ }
+ cb = nl_cb_alloc(NL_CB_DEFAULT);
+ if (!cb) {
+ DBG("Failed to allocate callbacks");
+ nlmsg_free(msg);
+ nlmsg_free(ssids);
+ return -ENOMEM;
+ }
+
+ /** Set nl message and callback functions. */
+ genlmsg_put(msg, 0, 0, id, 0, 0, NL80211_CMD_TRIGGER_SCAN, 0);
+ nla_put_u32(msg, NL80211_ATTR_IFINDEX, if_index);
+ nla_put(ssids, 1, 0, "");
+ nla_put_nested(msg, NL80211_ATTR_SCAN_SSIDS, ssids);
+ nlmsg_free(ssids);
+
+ nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, __netconfig_netlink_scan_reply, &results);
+ nl_cb_err(cb, NL_CB_CUSTOM, error_handler, &err);
+ nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, finish_handler, &err);
+ nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, ack_handler, &err);
+ nl_cb_set(cb, NL_CB_SEQ_CHECK, NL_CB_CUSTOM, no_seq_check, NULL);
+
+ /** Send NL80211_CMD_TRIGGER_SCAN to start the scan. */
+ err = 1;
+ ret = nl_send_auto_complete(socket, msg);
+ DBG("Sent %d bytes to the kernel", ret);
+ while (err > 0)
+ ret = nl_recvmsgs(socket, cb);
+
+ if (ret < 0) {
+ DBG("nl_recvmsgs() ret: %d (%s)", ret, nl_geterror(-ret));
+ return ret;
+ }
+
+ while (!results.done)
+ nl_recvmsgs(socket, cb);
+
+ if (results.aborted) {
+ DBG("scan aborted");
+ return 1;
+ }
+ DBG("Scan done");
+
+ /** Release memory */
+ nlmsg_free(msg);
+ nl_cb_put(cb);
+ nl_socket_drop_membership(socket, mcid);
+ return 0;
+}
+
+gboolean handle_netlink_scan(Wifi *wifi, GDBusMethodInvocation *context)
+{
+ DBG("");
+ int if_index = __netconfig_get_interface_index(WIFI_IFNAME);
+
+ /** Open socket to kernel. */
+ struct nl_sock *socket = nl_socket_alloc();
+ genl_connect(socket);
+ int id = genl_ctrl_resolve(socket, "nl80211");
+
+ /** Request NL80211_CMD_TRIGGER_SCAN to the kernel. */
+ int err = __netconfig_request_netlink_scan(socket, if_index, id);
+ if (err != 0) {
+ DBG("__netconfig_request_netlink_scan() failed, error %d", err);
+ wifi_complete_netlink_scan(wifi, context);
+ return err;
+ }
+
+ /** Get info of all available APs. */
+ struct nl_msg *msg = nlmsg_alloc();
+ genlmsg_put(msg, 0, 0, id, 0, NLM_F_DUMP, NL80211_CMD_GET_SCAN, 0);
+ nla_put_u32(msg, NL80211_ATTR_IFINDEX, if_index);
+ nl_socket_modify_cb(socket, NL_CB_VALID, NL_CB_CUSTOM, __netconfig_netlink_scan_cb, NULL);
+
+ int ret = nl_send_auto_complete(socket, msg);
+ DBG("NL80211_CMD_GET_SCAN sent %d bytes to the kernel", ret);
+
+ /** Receive the kernel message. */
+ ret = nl_recvmsgs_default(socket);
+ nlmsg_free(msg);
+ if (ret < 0) {
+ DBG("nl_recvmsgs_default() failed. ret: %d (error: %s)", ret, nl_geterror(-ret));
+ wifi_complete_netlink_scan(wifi, context);
+ return ret;
+ }
+
+ wifi_complete_netlink_scan(wifi, context);
+ return TRUE;
+}