From: Niraj Kumar Goit Date: Fri, 17 Nov 2017 03:43:44 +0000 (+0530) Subject: [net-config] Added Support of Netlink scan. X-Git-Tag: submit/tizen/20171130.074734~3 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=421824035be57c9a77e7248f8dbce0813ea36639;p=platform%2Fcore%2Fconnectivity%2Fnet-config.git [net-config] Added Support of Netlink scan. 'NetlinkScan' Dbus method is added to directly trigger driver scan when wifi is in deactivated state. Change-Id: Ic5ec451de8f07458c3473e949bd68b999d408df0 Signed-off-by: Niraj Kumar Goit --- diff --git a/CMakeLists.txt b/CMakeLists.txt index 1069cdd..8ac8f89 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,6 +15,7 @@ SET(SRCS src/wifi-eap.c src/wifi-wps.c src/wifi-bssid-scan.c + src/wifi-netlink-scan.c src/wifi-agent.c src/wifi-power.c src/wifi-state.c @@ -81,6 +82,7 @@ PKG_CHECK_MODULES(pkgs REQUIRED gio-unix-2.0 capi-system-info libtzplatform-config + libnl-2.0 ${P2P_REQUIRED_PKGS} ${WEARABLE_REQUIRED_PKGS} ) diff --git a/include/util.h b/include/util.h index 4115fc4..299d8d3 100755 --- a/include/util.h +++ b/include/util.h @@ -51,6 +51,7 @@ gboolean netconfig_is_wifi_tethering_on(void); gboolean netconfig_interface_up(const char *ifname); gboolean netconfig_interface_down(const char *ifname); +int __netconfig_get_interface_index(const char *interface_name); int netconfig_execute_cmd(const char *cmd); int netconfig_execute_file(const char *file_path, char *const args[], char *const env[]); diff --git a/include/wifi-netlink-scan.h b/include/wifi-netlink-scan.h new file mode 100755 index 0000000..e0c52c3 --- /dev/null +++ b/include/wifi-netlink-scan.h @@ -0,0 +1,73 @@ +/* + * 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. + * + */ + +#ifndef __NETCONFIG_WIFI_NETLINK_SCAN_H__ +#define __NETCONFIG_WIFI_NETLINK_SCAN_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "wifi.h" + +#define NETCONFIG_SSID_LEN 32 +#define NETCONFIG_BSSID_LEN 17 +#define NETCONFIG_MAX_VSIE_LEN 255 + +#define NL80211_CMD_SCAN_ABORTED 35 +#define NL80211_CMD_GET_SCAN 32 +#define NL80211_CMD_TRIGGER_SCAN 33 +#define NL80211_CMD_NEW_SCAN_RESULTS 34 +#define NL80211_BSS_BSSID 1 +#define NL80211_BSS_FREQUENCY 2 +#define NL80211_BSS_INFORMATION_ELEMENTS 6 +#define NL80211_BSS_SIGNAL_MBM 7 +#define NL80211_BSS_MAX 15 +#define NL80211_ATTR_BSS 47 +#define NL80211_ATTR_IFINDEX 3 +#define NL80211_ATTR_SCAN_SSIDS 45 +#define NL80211_ATTR_MAX 221 + +struct bss_scan_info_t{ + char bssid[NETCONFIG_BSSID_LEN+1]; + char ssid[NETCONFIG_SSID_LEN+1]; + char vsie[NETCONFIG_MAX_VSIE_LEN+1]; + int ssid_len; + int freq; + int signal; +}; + +struct netconfig_netlink_scan_results { + int done; + int aborted; +}; + +struct netconfig_netlink_scan_handler_args { + const char *group; + int id; +}; + +void __netconfig_notify_netlink_scan_done(void); +gboolean handle_netlink_scan(Wifi *wifi, GDBusMethodInvocation *context); + +#ifdef __cplusplus +} +#endif + +#endif /* __NETCONFIG_WIFI_NETLINK_SCAN_H__ */ diff --git a/interfaces/netconfig-iface-wifi.xml b/interfaces/netconfig-iface-wifi.xml index 54c665a..1065910 100755 --- a/interfaces/netconfig-iface-wifi.xml +++ b/interfaces/netconfig-iface-wifi.xml @@ -45,6 +45,8 @@ + + @@ -175,6 +177,9 @@ + + + diff --git a/packaging/net-config.spec b/packaging/net-config.spec index 7805980..15ef805 100755 --- a/packaging/net-config.spec +++ b/packaging/net-config.spec @@ -14,6 +14,7 @@ BuildRequires: pkgconfig(capi-vpnsvc) BuildRequires: cmake BuildRequires: pkgconfig(sqlite3) BuildRequires: pkgconfig(libtzplatform-config) +BuildRequires: pkgconfig(libnl-2.0) Requires: vconf Requires: connman Requires: systemd diff --git a/resources/etc/dbus-1/system.d/net-config.conf b/resources/etc/dbus-1/system.d/net-config.conf index 66997da..dcef481 100755 --- a/resources/etc/dbus-1/system.d/net-config.conf +++ b/resources/etc/dbus-1/system.d/net-config.conf @@ -75,6 +75,7 @@ + diff --git a/src/utils/util.c b/src/utils/util.c index b5d96a6..762fe24 100755 --- a/src/utils/util.c +++ b/src/utils/util.c @@ -557,6 +557,7 @@ int __netconfig_get_interface_index(const char *interface_name) close(sock); if (result < 0) { + strerror_r(errno, error_buf, MAX_SIZE_ERROR_BUFFER); DBG("Failed to get ifr index: %s", error_buf); return -1; } diff --git a/src/wifi-netlink-scan.c b/src/wifi-netlink-scan.c new file mode 100755 index 0000000..7c3f9b2 --- /dev/null +++ b/src/wifi-netlink-scan.c @@ -0,0 +1,488 @@ +/* + * 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 + +#include "netsupplicant.h" +#include "log.h" +#include "util.h" +#include "wifi-netlink-scan.h" +#include +#include +#include +#include +#include +#include +#include + +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; +} diff --git a/src/wifi.c b/src/wifi.c index 3ec8d94..a50f8bd 100755 --- a/src/wifi.c +++ b/src/wifi.c @@ -31,6 +31,7 @@ #include "wifi-eap.h" #include "wifi-wps.h" #include "wifi-bssid-scan.h" +#include "wifi-netlink-scan.h" #include "wifi-power.h" #include "wifi-state.h" #include "wifi-agent.h" @@ -166,6 +167,8 @@ void wifi_object_create_and_init(void) G_CALLBACK(handle_request_bssid_scan), NULL); g_signal_connect(wifi_object, "handle-get-bssid-list", G_CALLBACK(handle_get_bssid_list), NULL); + g_signal_connect(wifi_object, "handle-netlink-scan", + G_CALLBACK(handle_netlink_scan), NULL); /* WPS Connect */ g_signal_connect(wifi_object, "handle-request-wps-connect",