mirror of
https://git.kernel.org/pub/scm/network/wireless/iwd.git
synced 2024-12-23 06:02:37 +01:00
438 lines
11 KiB
C
438 lines
11 KiB
C
/*
|
|
*
|
|
* Wireless daemon for Linux
|
|
*
|
|
* Copyright (C) 2013-2019 Intel Corporation. All rights reserved.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include <linux/if_ether.h>
|
|
|
|
#include <ell/ell.h>
|
|
|
|
#include "src/iwd.h"
|
|
#include "src/module.h"
|
|
#include "src/util.h"
|
|
#include "src/wiphy.h"
|
|
#include "src/scan.h"
|
|
#include "src/netdev.h"
|
|
#include "src/dbus.h"
|
|
#include "src/frame-xchg.h"
|
|
#include "src/station.h"
|
|
|
|
struct device {
|
|
uint32_t index;
|
|
|
|
struct wiphy *wiphy;
|
|
struct netdev *netdev;
|
|
|
|
bool powered : 1; /* Current IFUP state */
|
|
bool dbus_powered : 1; /* Last IFUP state wanted via D-Bus */
|
|
|
|
uint32_t wiphy_rfkill_watch;
|
|
};
|
|
|
|
static uint32_t netdev_watch;
|
|
|
|
static void device_ap_roam_frame_event(const struct mmpdu_header *hdr,
|
|
const void *body, size_t body_len, int rssi, void *user_data)
|
|
{
|
|
struct device *device = user_data;
|
|
struct station *station = station_find(device->index);
|
|
|
|
if (!station)
|
|
return;
|
|
|
|
station_ap_directed_roam(station, hdr, body, body_len);
|
|
}
|
|
|
|
static bool device_property_get_name(struct l_dbus *dbus,
|
|
struct l_dbus_message *message,
|
|
struct l_dbus_message_builder *builder,
|
|
void *user_data)
|
|
{
|
|
struct device *device = user_data;
|
|
|
|
l_dbus_message_builder_append_basic(builder, 's',
|
|
netdev_get_name(device->netdev));
|
|
return true;
|
|
}
|
|
|
|
static bool device_property_get_address(struct l_dbus *dbus,
|
|
struct l_dbus_message *message,
|
|
struct l_dbus_message_builder *builder,
|
|
void *user_data)
|
|
{
|
|
struct device *device = user_data;
|
|
const char *str;
|
|
|
|
str = util_address_to_string(netdev_get_address(device->netdev));
|
|
l_dbus_message_builder_append_basic(builder, 's', str);
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool device_property_get_powered(struct l_dbus *dbus,
|
|
struct l_dbus_message *message,
|
|
struct l_dbus_message_builder *builder,
|
|
void *user_data)
|
|
{
|
|
struct device *device = user_data;
|
|
bool powered = device->powered;
|
|
|
|
l_dbus_message_builder_append_basic(builder, 'b', &powered);
|
|
|
|
return true;
|
|
}
|
|
|
|
struct set_generic_cb_data {
|
|
struct device *device;
|
|
struct l_dbus *dbus;
|
|
struct l_dbus_message *message;
|
|
l_dbus_property_complete_cb_t complete;
|
|
};
|
|
|
|
static void set_generic_destroy(void *user_data)
|
|
{
|
|
struct set_generic_cb_data *cb_data = user_data;
|
|
|
|
/* Message hasn't been replied to, generate an Aborted error */
|
|
if (cb_data->message)
|
|
cb_data->complete(cb_data->dbus, cb_data->message,
|
|
dbus_error_aborted(cb_data->message));
|
|
|
|
l_free(cb_data);
|
|
}
|
|
|
|
static void set_powered_cb(struct netdev *netdev, int result, void *user_data)
|
|
{
|
|
struct set_generic_cb_data *cb_data = user_data;
|
|
struct l_dbus_message *reply = NULL;
|
|
|
|
if (result < 0)
|
|
reply = dbus_error_failed(cb_data->message);
|
|
|
|
cb_data->complete(cb_data->dbus, cb_data->message, reply);
|
|
cb_data->message = NULL;
|
|
}
|
|
|
|
static struct l_dbus_message *device_property_set_powered(struct l_dbus *dbus,
|
|
struct l_dbus_message *message,
|
|
struct l_dbus_message_iter *new_value,
|
|
l_dbus_property_complete_cb_t complete,
|
|
void *user_data)
|
|
{
|
|
struct device *device = user_data;
|
|
bool powered;
|
|
struct set_generic_cb_data *cb_data;
|
|
int r;
|
|
|
|
if (!l_dbus_message_iter_get_variant(new_value, "b", &powered))
|
|
return dbus_error_invalid_args(message);
|
|
|
|
device->dbus_powered = powered;
|
|
|
|
if (powered == device->powered) {
|
|
complete(dbus, message, NULL);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
cb_data = l_new(struct set_generic_cb_data, 1);
|
|
cb_data->device = device;
|
|
cb_data->dbus = dbus;
|
|
cb_data->message = message;
|
|
cb_data->complete = complete;
|
|
|
|
r = netdev_set_powered(device->netdev, powered, set_powered_cb,
|
|
cb_data, set_generic_destroy);
|
|
if (r < 0) {
|
|
l_free(cb_data);
|
|
return dbus_error_from_errno(r, message);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static bool device_property_get_adapter(struct l_dbus *dbus,
|
|
struct l_dbus_message *message,
|
|
struct l_dbus_message_builder *builder,
|
|
void *user_data)
|
|
{
|
|
struct device *device = user_data;
|
|
|
|
l_dbus_message_builder_append_basic(builder, 'o',
|
|
wiphy_get_path(device->wiphy));
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool device_property_get_mode(struct l_dbus *dbus,
|
|
struct l_dbus_message *message,
|
|
struct l_dbus_message_builder *builder,
|
|
void *user_data)
|
|
{
|
|
struct device *device = user_data;
|
|
uint32_t iftype = netdev_get_iftype(device->netdev);
|
|
const char *modestr = dbus_iftype_to_string(iftype);
|
|
|
|
if (modestr == NULL)
|
|
modestr = "unknown";
|
|
|
|
l_dbus_message_builder_append_basic(builder, 's', modestr);
|
|
|
|
return true;
|
|
}
|
|
|
|
static void set_mode_cb(struct netdev *netdev, int result, void *user_data)
|
|
{
|
|
struct set_generic_cb_data *cb_data = user_data;
|
|
struct l_dbus_message *reply = NULL;
|
|
|
|
if (result < 0)
|
|
reply = dbus_error_from_errno(result, cb_data->message);
|
|
|
|
cb_data->complete(cb_data->dbus, cb_data->message, reply);
|
|
cb_data->message = NULL;
|
|
|
|
l_dbus_property_changed(cb_data->dbus,
|
|
netdev_get_path(cb_data->device->netdev),
|
|
IWD_DEVICE_INTERFACE, "Mode");
|
|
}
|
|
|
|
static struct l_dbus_message *device_property_set_mode(struct l_dbus *dbus,
|
|
struct l_dbus_message *message,
|
|
struct l_dbus_message_iter *new_value,
|
|
l_dbus_property_complete_cb_t complete,
|
|
void *user_data)
|
|
{
|
|
struct device *device = user_data;
|
|
struct netdev *netdev = device->netdev;
|
|
const char *mode;
|
|
enum netdev_iftype iftype;
|
|
int r;
|
|
struct set_generic_cb_data *cb_data;
|
|
|
|
if (!l_dbus_message_iter_get_variant(new_value, "s", &mode))
|
|
return dbus_error_invalid_args(message);
|
|
|
|
if (!strcmp(mode, "station"))
|
|
iftype = NETDEV_IFTYPE_STATION;
|
|
else if (!strcmp(mode, "ap"))
|
|
iftype = NETDEV_IFTYPE_AP;
|
|
else if (!strcmp(mode, "ad-hoc"))
|
|
iftype = NETDEV_IFTYPE_ADHOC;
|
|
else
|
|
return dbus_error_invalid_args(message);
|
|
|
|
if (iftype == netdev_get_iftype(netdev)) {
|
|
complete(dbus, message, NULL);
|
|
return NULL;
|
|
}
|
|
|
|
cb_data = l_new(struct set_generic_cb_data, 1);
|
|
cb_data->device = device;
|
|
cb_data->dbus = dbus;
|
|
cb_data->message = message;
|
|
cb_data->complete = complete;
|
|
|
|
r = netdev_set_iftype(device->netdev, iftype, set_mode_cb,
|
|
cb_data, set_generic_destroy);
|
|
if (r < 0) {
|
|
l_free(cb_data);
|
|
return dbus_error_from_errno(r, message);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void setup_device_interface(struct l_dbus_interface *interface)
|
|
{
|
|
l_dbus_interface_property(interface, "Name", 0, "s",
|
|
device_property_get_name, NULL);
|
|
l_dbus_interface_property(interface, "Address", 0, "s",
|
|
device_property_get_address, NULL);
|
|
l_dbus_interface_property(interface, "Powered", 0, "b",
|
|
device_property_get_powered,
|
|
device_property_set_powered);
|
|
l_dbus_interface_property(interface, "Adapter", 0, "o",
|
|
device_property_get_adapter, NULL);
|
|
l_dbus_interface_property(interface, "Mode", 0, "s",
|
|
device_property_get_mode,
|
|
device_property_set_mode);
|
|
}
|
|
|
|
static void device_wiphy_state_changed_event(struct wiphy *wiphy,
|
|
enum wiphy_state_watch_event event,
|
|
void *user_data)
|
|
{
|
|
struct device *device = user_data;
|
|
|
|
switch (event) {
|
|
case WIPHY_STATE_WATCH_EVENT_RFKILLED:
|
|
break;
|
|
case WIPHY_STATE_WATCH_EVENT_POWERED:
|
|
if (device->dbus_powered)
|
|
netdev_set_powered(device->netdev, true,
|
|
NULL, NULL, NULL);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static struct device *device_create(struct wiphy *wiphy, struct netdev *netdev)
|
|
{
|
|
struct device *device;
|
|
struct l_dbus *dbus = dbus_get_bus();
|
|
uint32_t ifindex = netdev_get_ifindex(netdev);
|
|
const uint8_t action_ap_roam_prefix[2] = { 0x0a, 0x07 };
|
|
|
|
device = l_new(struct device, 1);
|
|
device->index = ifindex;
|
|
device->wiphy = wiphy;
|
|
device->netdev = netdev;
|
|
|
|
if (!l_dbus_object_add_interface(dbus, netdev_get_path(device->netdev),
|
|
IWD_DEVICE_INTERFACE, device))
|
|
l_info("Unable to register %s interface", IWD_DEVICE_INTERFACE);
|
|
|
|
if (!l_dbus_object_add_interface(dbus, netdev_get_path(device->netdev),
|
|
L_DBUS_INTERFACE_PROPERTIES, device))
|
|
l_info("Unable to register %s interface",
|
|
L_DBUS_INTERFACE_PROPERTIES);
|
|
|
|
/*
|
|
* register for AP roam transition watch
|
|
*/
|
|
frame_watch_add(netdev_get_wdev_id(netdev), 0, 0x00d0,
|
|
action_ap_roam_prefix, sizeof(action_ap_roam_prefix),
|
|
device_ap_roam_frame_event, device, NULL);
|
|
|
|
device->powered = netdev_get_is_up(netdev);
|
|
|
|
device->dbus_powered = true;
|
|
device->wiphy_rfkill_watch =
|
|
wiphy_state_watch_add(wiphy, device_wiphy_state_changed_event,
|
|
device, NULL);
|
|
|
|
return device;
|
|
}
|
|
|
|
static void device_free(struct device *device)
|
|
{
|
|
l_debug("");
|
|
|
|
wiphy_state_watch_remove(device->wiphy, device->wiphy_rfkill_watch);
|
|
|
|
/*
|
|
* We're triggered on NETDEV_WATCH_EVENT_DEL or device_exit. The former
|
|
* is triggered on NL80211_CMD_DEL_INTERFACE and RTM_DELLINK which
|
|
* also cause all frame watches to be unregistered so we don't have
|
|
* to do this here. device_exit is triggered under the same conditions
|
|
* as frame_xchg_exit.
|
|
*/
|
|
|
|
l_free(device);
|
|
}
|
|
|
|
static void device_netdev_notify(struct netdev *netdev,
|
|
enum netdev_watch_event event,
|
|
void *user_data)
|
|
{
|
|
struct device *device;
|
|
struct l_dbus *dbus = dbus_get_bus();
|
|
const char *path = netdev_get_path(netdev);
|
|
|
|
device = l_dbus_object_get_data(dbus, path, IWD_DEVICE_INTERFACE);
|
|
|
|
if (!device && event != NETDEV_WATCH_EVENT_NEW)
|
|
return;
|
|
|
|
switch (event) {
|
|
case NETDEV_WATCH_EVENT_NEW:
|
|
if (L_WARN_ON(device))
|
|
break;
|
|
|
|
if (netdev_get_iftype(netdev) == NETDEV_IFTYPE_P2P_CLIENT ||
|
|
netdev_get_iftype(netdev) ==
|
|
NETDEV_IFTYPE_P2P_GO)
|
|
return;
|
|
|
|
device_create(netdev_get_wiphy(netdev), netdev);
|
|
break;
|
|
case NETDEV_WATCH_EVENT_DEL:
|
|
l_dbus_unregister_object(dbus, path);
|
|
break;
|
|
case NETDEV_WATCH_EVENT_UP:
|
|
device->powered = true;
|
|
|
|
l_dbus_property_changed(dbus, path,
|
|
IWD_DEVICE_INTERFACE, "Powered");
|
|
break;
|
|
case NETDEV_WATCH_EVENT_DOWN:
|
|
device->powered = false;
|
|
|
|
l_dbus_property_changed(dbus, path,
|
|
IWD_DEVICE_INTERFACE, "Powered");
|
|
break;
|
|
case NETDEV_WATCH_EVENT_NAME_CHANGE:
|
|
l_dbus_property_changed(dbus, path,
|
|
IWD_DEVICE_INTERFACE, "Name");
|
|
break;
|
|
case NETDEV_WATCH_EVENT_ADDRESS_CHANGE:
|
|
l_dbus_property_changed(dbus, path,
|
|
IWD_DEVICE_INTERFACE, "Address");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void destroy_device_interface(void *user_data)
|
|
{
|
|
struct device *device = user_data;
|
|
|
|
device_free(device);
|
|
}
|
|
|
|
static int device_init(void)
|
|
{
|
|
if (!l_dbus_register_interface(dbus_get_bus(),
|
|
IWD_DEVICE_INTERFACE,
|
|
setup_device_interface,
|
|
destroy_device_interface, false))
|
|
return false;
|
|
|
|
netdev_watch = netdev_watch_add(device_netdev_notify, NULL, NULL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void device_exit(void)
|
|
{
|
|
netdev_watch_remove(netdev_watch);
|
|
|
|
l_dbus_unregister_interface(dbus_get_bus(), IWD_DEVICE_INTERFACE);
|
|
}
|
|
|
|
IWD_MODULE(device, device_init, device_exit)
|