diff --git a/src/manager.c b/src/manager.c index a5735de4..3d1534ea 100644 --- a/src/manager.c +++ b/src/manager.c @@ -35,19 +35,284 @@ #include "linux/nl80211.h" #include "src/iwd.h" +#include "src/netdev.h" #include "src/wiphy.h" #include "src/util.h" #include "src/common.h" static struct l_genl_family *nl80211 = NULL; +struct wiphy_setup_state { + uint32_t id; + struct wiphy *wiphy; + struct l_timeout *setup_timeout; + unsigned int pending_cmd_count; + bool aborted; +}; + +static struct l_queue *pending_wiphys; + +static void wiphy_setup_state_free(void *data) +{ + struct wiphy_setup_state *state = data; + + l_queue_remove(pending_wiphys, state); + + if (state->setup_timeout) + l_timeout_remove(state->setup_timeout); + + L_WARN_ON(state->pending_cmd_count); + l_free(state); +} + +static void manager_new_interface_cb(struct l_genl_msg *msg, void *user_data) +{ + struct wiphy_setup_state *state = user_data; + + l_debug(""); + + if (state->aborted) + return; + + if (l_genl_msg_get_error(msg) < 0) { + l_error("NEW_INTERFACE failed: %s", + strerror(-l_genl_msg_get_error(msg))); + /* + * Nothing we can do to use this wiphy since by now we + * will have successfully deleted any default interface + * there may have been. + */ + return; + } + + netdev_create_from_genl(msg); +} + +static void manager_new_interface_done(void *user_data) +{ + struct wiphy_setup_state *state = user_data; + + state->pending_cmd_count--; + wiphy_setup_state_free(state); +} + +static void manager_create_interfaces(struct wiphy_setup_state *state) +{ + struct l_genl_msg *msg; + char ifname[10]; + uint32_t iftype = NL80211_IFTYPE_STATION; + unsigned cmd_id; + + if (state->aborted) { + wiphy_setup_state_free(state); + return; + } + + /* + * Current policy: we maintain one netdev per wiphy for station, + * AP and Ad-Hoc modes, one optional p2p-device and zero or more + * p2p-GOs or p2p-clients. The P2P-related interfaces will be + * created on request. + */ + + /* To be improved */ + snprintf(ifname, sizeof(ifname), "wlan%i", (int) state->id); + l_debug("creating %s", ifname); + + msg = l_genl_msg_new(NL80211_CMD_NEW_INTERFACE); + l_genl_msg_append_attr(msg, NL80211_ATTR_WIPHY, 4, &state->id); + l_genl_msg_append_attr(msg, NL80211_ATTR_IFTYPE, 4, &iftype); + l_genl_msg_append_attr(msg, NL80211_ATTR_IFNAME, + strlen(ifname) + 1, ifname); + l_genl_msg_append_attr(msg, NL80211_ATTR_4ADDR, 1, "\0"); + l_genl_msg_append_attr(msg, NL80211_ATTR_SOCKET_OWNER, 0, ""); + cmd_id = l_genl_family_send(nl80211, msg, + manager_new_interface_cb, state, + manager_new_interface_done); + + if (!cmd_id) { + l_error("Sending NEW_INTERFACE for %s", ifname); + wiphy_setup_state_free(state); + return; + } + + state->pending_cmd_count++; +} + +static void manager_setup_cmd_done(void *user_data) +{ + struct wiphy_setup_state *state = user_data; + + state->pending_cmd_count--; + + if (!state->pending_cmd_count) + manager_create_interfaces(state); +} + +static void manager_del_interface_cb(struct l_genl_msg *msg, void *user_data) +{ + struct wiphy_setup_state *state = user_data; + + l_debug(""); + + if (state->aborted) + return; + + if (l_genl_msg_get_error(msg) < 0) + l_error("DEL_INTERFACE failed: %s", + strerror(-l_genl_msg_get_error(msg))); +} + +static void manager_get_interface_cb(struct l_genl_msg *msg, void *user_data) +{ + struct wiphy_setup_state *state = user_data; + struct l_genl_attr attr; + uint16_t type, len; + const void *data; + const uint32_t *ifindex = NULL, *iftype = NULL; + const uint64_t *wdev_idx = NULL; + const char *ifname = NULL; + struct l_genl_msg *del_msg; + unsigned cmd_id; + + l_debug(""); + + if (state->aborted) + return; + + if (!l_genl_attr_init(&attr, msg)) + return; + + while (l_genl_attr_next(&attr, &type, &len, &data)) { + switch (type) { + case NL80211_ATTR_IFINDEX: + if (len != sizeof(uint32_t)) { + l_warn("Invalid interface index attribute"); + return; + } + + ifindex = data; + break; + + case NL80211_ATTR_WDEV: + if (len != sizeof(uint64_t)) { + l_warn("Invalid wdev index attribute"); + return; + } + + wdev_idx = data; + break; + + + case NL80211_ATTR_WIPHY: + if (len != sizeof(uint32_t) || + *((uint32_t *) data) != state->id) { + l_warn("Invalid wiphy attribute"); + return; + } + + break; + + case NL80211_ATTR_IFTYPE: + if (len != sizeof(uint32_t)) { + l_warn("Invalid interface type attribute"); + return; + } + + iftype = data; + break; + + case NL80211_ATTR_IFNAME: + if (len < 1 || !memchr(data + 1, 0, len - 1)) { + l_warn("Invalid interface name attribute"); + return; + } + + ifname = data; + break; + } + } + + if (!ifindex || !wdev_idx || !iftype || !ifname) + return; + + del_msg = l_genl_msg_new(NL80211_CMD_DEL_INTERFACE); + l_genl_msg_append_attr(del_msg, NL80211_ATTR_IFINDEX, 4, ifindex); + l_genl_msg_append_attr(del_msg, NL80211_ATTR_WDEV, 8, wdev_idx); + l_genl_msg_append_attr(del_msg, NL80211_ATTR_WIPHY, 4, &state->id); + cmd_id = l_genl_family_send(nl80211, del_msg, + manager_del_interface_cb, state, + manager_setup_cmd_done); + + if (!cmd_id) { + l_error("Sending DEL_INTERFACE for %s failed", ifname); + return; + } + + l_debug(""); + state->pending_cmd_count++; +} + +static void manager_wiphy_dump_interfaces(struct wiphy_setup_state *state) +{ + struct l_genl_msg *msg; + unsigned cmd_id; + + if (state->setup_timeout) { + l_timeout_remove(state->setup_timeout); + state->setup_timeout = NULL; + } + + /* + * As the first step after new wiphy is detected we will query + * the initial interface setup, delete the default interfaces + * and create interfaces for our own use with NL80211_ATTR_SOCKET_OWNER + * on them. After that if new interfaces are created outside of + * IWD, or removed outside of IWD, we don't touch them and will + * try to minimally adapt to handle the removals correctly. It's + * a very unlikely situation in any case but it wouldn't make + * sense to try to continually enforce our setup fighting against + * some other process, and it wouldn't make sense to try to + * manage and use additional interfaces beyond the one or two + * we need for our operations. + */ + + msg = l_genl_msg_new(NL80211_CMD_GET_INTERFACE); + l_genl_msg_append_attr(msg, NL80211_ATTR_WIPHY, 4, &state->id); + cmd_id = l_genl_family_dump(nl80211, msg, + manager_get_interface_cb, state, + manager_setup_cmd_done); + + if (!cmd_id) { + l_error("Querying interface information for wiphy %u failed", + (unsigned int) state->id); + wiphy_setup_state_free(state); + return; + } + + l_debug(""); + state->pending_cmd_count++; +} + +static void manager_wiphy_setup_timeout(struct l_timeout *timeout, + void *user_data) +{ + struct wiphy_setup_state *state = user_data; + + manager_wiphy_dump_interfaces(state); +} + static void manager_new_wiphy_event(struct l_genl_msg *msg) { + struct wiphy_setup_state *state; struct wiphy *wiphy; struct l_genl_attr attr; uint32_t id; const char *name; + if (!pending_wiphys) + return; + if (!l_genl_attr_init(&attr, msg)) return; @@ -63,6 +328,56 @@ static void manager_new_wiphy_event(struct l_genl_msg *msg) } wiphy_update_from_genl(wiphy, msg); + + /* + * We've got a new wiphy, flag it as new and wait for a + * NEW_INTERFACE event for this wiphy's default driver-created + * interface. That event's handler will check the flag and + * finish setting up the interfaces for this new wiphy and then + * clear the flag. In some corner cases there may be no + * default interface on this wiphy and no user-space created + * interfaces from before IWD started, so set a 1-second timeout + * for the event. The timeout pointer is also used as the flag. + */ + + state = l_new(struct wiphy_setup_state, 1); + state->id = id; + state->wiphy = wiphy; + state->setup_timeout = l_timeout_create(1, manager_wiphy_setup_timeout, + state, NULL); + l_queue_push_tail(pending_wiphys, state); +} + +static bool manager_wiphy_state_match(const void *a, const void *b) +{ + const struct wiphy_setup_state *state = a; + uint32_t id = L_PTR_TO_UINT(b); + + return (state->id == id); +} + +static struct wiphy_setup_state *manager_find_pending(uint32_t id) +{ + return l_queue_find(pending_wiphys, manager_wiphy_state_match, + L_UINT_TO_PTR(id)); +} + +static uint32_t manager_parse_ifindex(struct l_genl_attr *attr) +{ + uint16_t type, len; + const void *data; + + while (l_genl_attr_next(attr, &type, &len, &data)) { + if (type != NL80211_ATTR_IFINDEX) + continue; + + if (len != sizeof(uint32_t)) + break; + + return *((uint32_t *) data); + } + + return -1; } static uint32_t manager_parse_wiphy_id(struct l_genl_attr *attr) @@ -85,6 +400,7 @@ static uint32_t manager_parse_wiphy_id(struct l_genl_attr *attr) static void manager_del_wiphy_event(struct l_genl_msg *msg) { + struct wiphy_setup_state *state; struct wiphy *wiphy; struct l_genl_attr attr; uint32_t id; @@ -94,6 +410,14 @@ static void manager_del_wiphy_event(struct l_genl_msg *msg) id = manager_parse_wiphy_id(&attr); + state = manager_find_pending(id); + if (state) { + if (state->pending_cmd_count) + state->aborted = true; + else + wiphy_setup_state_free(state); + } + wiphy = wiphy_find(id); if (wiphy) wiphy_destroy(wiphy); @@ -102,6 +426,9 @@ static void manager_del_wiphy_event(struct l_genl_msg *msg) static void manager_config_notify(struct l_genl_msg *msg, void *user_data) { uint8_t cmd; + struct wiphy_setup_state *state; + struct l_genl_attr attr; + struct netdev *netdev; cmd = l_genl_msg_get_command(msg); @@ -115,6 +442,33 @@ static void manager_config_notify(struct l_genl_msg *msg, void *user_data) case NL80211_CMD_DEL_WIPHY: manager_del_wiphy_event(msg); break; + + case NL80211_CMD_NEW_INTERFACE: + /* + * If we have a NEW_INTERFACE for a freshly detected wiphy + * assume we can now query for the default or pre-created + * interfaces, remove any we don't need and create our own. + */ + if (!l_genl_attr_init(&attr, msg)) + break; + + state = manager_find_pending(manager_parse_wiphy_id(&attr)); + if (!state || !state->setup_timeout) + break; + + manager_wiphy_dump_interfaces(state); + break; + + case NL80211_CMD_DEL_INTERFACE: + if (!l_genl_attr_init(&attr, msg)) + break; + + netdev = netdev_find(manager_parse_ifindex(&attr)); + if (!netdev) + break; + + netdev_destroy(netdev); + break; } } @@ -139,10 +493,15 @@ bool manager_init(struct l_genl_family *in) NULL, NULL)) l_error("Initial wiphy information dump failed"); + pending_wiphys = l_queue_new(); + return true; } void manager_exit(void) { + l_queue_destroy(pending_wiphys, NULL); + pending_wiphys = NULL; + nl80211 = NULL; }