service: Add frequency support to service
[platform/upstream/connman.git] / plugins / wifi.c
index b7055c3..7ab38c5 100644 (file)
@@ -61,10 +61,14 @@ struct wifi_data {
        struct connman_device *device;
        struct connman_network *network;
        struct connman_network *pending_network;
+       GSList *networks;
        GSupplicantInterface *interface;
        GSupplicantState state;
        connman_bool_t connected;
        connman_bool_t disconnecting;
+       connman_bool_t tethering;
+       connman_bool_t bridged;
+       const char *bridge;
        int index;
        unsigned flags;
        unsigned int watch;
@@ -72,43 +76,23 @@ struct wifi_data {
 
 static GList *iface_list = NULL;
 
-static int get_bssid(struct connman_device *device,
-                               unsigned char *bssid, unsigned int *bssid_len)
+static void handle_tethering(struct wifi_data *wifi)
 {
-       struct iwreq wrq;
-       char *ifname;
-       int ifindex;
-       int fd, err;
-
-       ifindex = connman_device_get_index(device);
-       if (ifindex < 0)
-               return -EINVAL;
-
-       ifname = connman_inet_ifname(ifindex);
-       if (ifname == NULL)
-               return -EINVAL;
-
-       fd = socket(PF_INET, SOCK_DGRAM, 0);
-       if (fd < 0) {
-               g_free(ifname);
-               return -EINVAL;
-       }
-
-       memset(&wrq, 0, sizeof(wrq));
-       strncpy(wrq.ifr_name, ifname, IFNAMSIZ);
+       if (wifi->tethering == FALSE)
+               return;
 
-       err = ioctl(fd, SIOCGIWAP, &wrq);
+       if (wifi->bridge == NULL)
+               return;
 
-       g_free(ifname);
-       close(fd);
+       if (wifi->bridged == TRUE)
+               return;
 
-       if (err < 0)
-               return -EIO;
+       DBG("index %d bridge %s", wifi->index, wifi->bridge);
 
-       memcpy(bssid, wrq.u.ap_addr.sa_data, ETH_ALEN);
-       *bssid_len = ETH_ALEN;
+       if (connman_inet_add_to_bridge(wifi->index, wifi->bridge) < 0)
+               return;
 
-       return 0;
+       wifi->bridged = TRUE;
 }
 
 static void wifi_newlink(unsigned flags, unsigned change, void *user_data)
@@ -129,9 +113,11 @@ static void wifi_newlink(unsigned flags, unsigned change, void *user_data)
        }
 
        if ((wifi->flags & IFF_LOWER_UP) != (flags & IFF_LOWER_UP)) {
-               if (flags & IFF_LOWER_UP)
+               if (flags & IFF_LOWER_UP) {
                        DBG("carrier on");
-               else
+
+                       handle_tethering(wifi);
+               } else
                        DBG("carrier off");
        }
 
@@ -150,6 +136,9 @@ static int wifi_probe(struct connman_device *device)
 
        wifi->connected = FALSE;
        wifi->disconnecting = FALSE;
+       wifi->tethering = FALSE;
+       wifi->bridged = FALSE;
+       wifi->bridge = NULL;
        wifi->state = G_SUPPLICANT_STATE_INACTIVE;
 
        connman_device_set_data(device, wifi);
@@ -166,6 +155,22 @@ static int wifi_probe(struct connman_device *device)
        return 0;
 }
 
+static void remove_networks(struct connman_device *device,
+                               struct wifi_data *wifi)
+{
+       GSList *list;
+
+       for (list = wifi->networks; list != NULL; list = list->next) {
+               struct connman_network *network = list->data;
+
+               connman_device_remove_network(device, network);
+               connman_network_unref(network);
+       }
+
+       g_slist_free(wifi->networks);
+       wifi->networks = NULL;
+}
+
 static void wifi_remove(struct connman_device *device)
 {
        struct wifi_data *wifi = connman_device_get_data(device);
@@ -177,10 +182,7 @@ static void wifi_remove(struct connman_device *device)
 
        iface_list = g_list_remove(iface_list, wifi);
 
-       if (wifi->pending_network != NULL) {
-               connman_network_unref(wifi->pending_network);
-               wifi->pending_network = NULL;
-       }
+       remove_networks(device, wifi);
 
        connman_device_set_data(device, NULL);
        connman_device_unref(wifi->device);
@@ -198,10 +200,11 @@ static void interface_create_callback(int result,
 {
        struct wifi_data *wifi = user_data;
 
-       DBG("result %d ifname %s", result,
-                               g_supplicant_interface_get_ifname(interface));
+       DBG("result %d ifname %s, wifi %p", result,
+                               g_supplicant_interface_get_ifname(interface),
+                               wifi);
 
-       if (result < 0)
+       if (result < 0 || wifi == NULL)
                return;
 
        wifi->interface = interface;
@@ -214,9 +217,9 @@ static void interface_remove_callback(int result,
 {
        struct wifi_data *wifi = user_data;
 
-       DBG("result %d", result);
+       DBG("result %d wifi %p", result, wifi);
 
-       if (result < 0)
+       if (result < 0 || wifi == NULL)
                return;
 
        wifi->interface = NULL;
@@ -228,31 +231,41 @@ static int wifi_enable(struct connman_device *device)
        struct wifi_data *wifi = connman_device_get_data(device);
        const char *interface = connman_device_get_string(device, "Interface");
        const char *driver = connman_option_get_string("wifi");
+       int ret;
 
        DBG("device %p %p", device, wifi);
 
-       return g_supplicant_interface_create(interface, driver, NULL,
+       ret = g_supplicant_interface_create(interface, driver, NULL,
                                                interface_create_callback,
                                                        wifi);
+       if (ret < 0)
+               return ret;
+
+       return -EINPROGRESS;
 }
 
 static int wifi_disable(struct connman_device *device)
 {
        struct wifi_data *wifi = connman_device_get_data(device);
+       int ret;
 
        DBG("device %p", device);
 
        wifi->connected = FALSE;
        wifi->disconnecting = FALSE;
 
-       if (wifi->pending_network != NULL) {
-               connman_network_unref(wifi->pending_network);
+       if (wifi->pending_network != NULL)
                wifi->pending_network = NULL;
-       }
 
-       return g_supplicant_interface_remove(wifi->interface,
+       remove_networks(device, wifi);
+
+       ret = g_supplicant_interface_remove(wifi->interface,
                                                interface_remove_callback,
                                                        wifi);
+       if (ret < 0)
+               return ret;
+
+       return -EINPROGRESS;
 }
 
 static void scan_callback(int result, GSupplicantInterface *interface,
@@ -266,6 +279,7 @@ static void scan_callback(int result, GSupplicantInterface *interface,
                connman_device_reset_scanning(device);
        else
                connman_device_set_scanning(device, FALSE);
+       connman_device_unref(device);
 }
 
 static int wifi_scan(struct connman_device *device)
@@ -275,10 +289,16 @@ static int wifi_scan(struct connman_device *device)
 
        DBG("device %p %p", device, wifi->interface);
 
+       if (wifi->tethering == TRUE)
+               return 0;
+
+       connman_device_ref(device);
        ret = g_supplicant_interface_scan(wifi->interface, scan_callback,
                                                                device);
        if (ret == 0)
                connman_device_set_scanning(device, TRUE);
+       else
+               connman_device_unref(device);
 
        return ret;
 }
@@ -318,13 +338,35 @@ static int network_probe(struct connman_network *network)
 
 static void network_remove(struct connman_network *network)
 {
+       struct connman_device *device = connman_network_get_device(network);
+       struct wifi_data *wifi;
+
        DBG("network %p", network);
+
+       wifi = connman_device_get_data(device);
+       if (wifi == NULL)
+               return;
+
+       if (wifi->network != network)
+               return;
+
+       wifi->network = NULL;
 }
 
 static void connect_callback(int result, GSupplicantInterface *interface,
                                                        void *user_data)
 {
-       connman_error("%s", __func__);
+       struct connman_network *network = user_data;
+
+       DBG("network %p result %d", network, result);
+
+       if (result == -ENOKEY) {
+               connman_network_set_error(network,
+                                       CONNMAN_NETWORK_ERROR_INVALID_KEY);
+       } else if (result < 0) {
+               connman_network_set_error(network,
+                                       CONNMAN_NETWORK_ERROR_CONFIGURE_FAIL);
+       }
 }
 
 static GSupplicantSecurity network_security(const char *security)
@@ -347,7 +389,7 @@ static GSupplicantSecurity network_security(const char *security)
 
 static void ssid_init(GSupplicantSSID *ssid, struct connman_network *network)
 {
-       const char *security, *passphrase;
+       const char *security, *passphrase, *agent_passphrase;
 
        memset(ssid, 0, sizeof(*ssid));
        ssid->mode = G_SUPPLICANT_MODE_INFRA;
@@ -358,9 +400,17 @@ static void ssid_init(GSupplicantSSID *ssid, struct connman_network *network)
        ssid->security = network_security(security);
        passphrase = connman_network_get_string(network,
                                                "WiFi.Passphrase");
-       if (passphrase == NULL || strlen(passphrase) == 0)
-               ssid->passphrase = NULL;
-        else
+       if (passphrase == NULL || strlen(passphrase) == 0) {
+
+               /* Use agent provided passphrase as a fallback */
+               agent_passphrase = connman_network_get_string(network,
+                                               "WiFi.AgentPassphrase");
+
+               if (agent_passphrase == NULL || strlen(agent_passphrase) == 0)
+                       ssid->passphrase = NULL;
+               else
+                       ssid->passphrase = agent_passphrase;
+       } else
                ssid->passphrase = passphrase;
 
        ssid->eap = connman_network_get_string(network, "WiFi.EAP");
@@ -378,6 +428,12 @@ static void ssid_init(GSupplicantSSID *ssid, struct connman_network *network)
                                                ssid->passphrase);
        /* We must have an identity for both PEAP and TLS */
        ssid->identity = connman_network_get_string(network, "WiFi.Identity");
+
+       /* Use agent provided identity as a fallback */
+       if (ssid->identity == NULL || strlen(ssid->identity) == 0)
+               ssid->identity = connman_network_get_string(network,
+                                                       "WiFi.AgentIdentity");
+
        ssid->ca_cert_path = connman_network_get_string(network,
                                                        "WiFi.CACertFile");
        ssid->client_cert_path = connman_network_get_string(network,
@@ -418,12 +474,12 @@ static int network_connect(struct connman_network *network)
        ssid_init(ssid, network);
 
        if (wifi->disconnecting == TRUE)
-               wifi->pending_network = connman_network_ref(network);
+               wifi->pending_network = network;
        else {
-               wifi->network = connman_network_ref(network);
+               wifi->network = network;
 
                return g_supplicant_interface_connect(interface, ssid,
-                                               connect_callback, NULL);
+                                               connect_callback, network);
        }
 
        return -EINPROGRESS;
@@ -444,8 +500,6 @@ static void disconnect_callback(int result, GSupplicantInterface *interface,
                 */
                if (result < 0)
                        connman_network_set_connected(wifi->network, FALSE);
-
-               connman_network_unref(wifi->network);
        }
 
        wifi->network = NULL;
@@ -454,7 +508,6 @@ static void disconnect_callback(int result, GSupplicantInterface *interface,
 
        if (wifi->pending_network != NULL) {
                network_connect(wifi->pending_network);
-               connman_network_unref(wifi->pending_network);
                wifi->pending_network = NULL;
        }
 
@@ -513,7 +566,8 @@ static void interface_added(GSupplicantInterface *interface)
        if (wifi == NULL)
                return;
 
-       DBG("ifname %s driver %s wifi %p", ifname, driver, wifi);
+       DBG("ifname %s driver %s wifi %p tethering %d",
+                       ifname, driver, wifi, wifi->tethering);
 
        if (wifi->device == NULL) {
                connman_error("WiFi device not set");
@@ -521,6 +575,10 @@ static void interface_added(GSupplicantInterface *interface)
        }
 
        connman_device_set_powered(wifi->device, TRUE);
+
+       if (wifi->tethering == TRUE)
+               return;
+
        wifi_scan(wifi->device);
 }
 
@@ -620,8 +678,6 @@ static void interface_state(GSupplicantInterface *interface)
        struct connman_device *device;
        struct wifi_data *wifi;
        GSupplicantState state = g_supplicant_interface_get_state(interface);
-       unsigned char bssid[ETH_ALEN];
-       unsigned int bssid_len;
        connman_bool_t wps;
 
        wifi = g_supplicant_interface_get_data(interface);
@@ -654,9 +710,6 @@ static void interface_state(GSupplicantInterface *interface)
                /* reset scan trigger and schedule background scan */
                connman_device_schedule_scan(device);
 
-               if (get_bssid(device, bssid, &bssid_len) == 0)
-                       connman_network_set_address(network,
-                                                       bssid, bssid_len);
                connman_network_set_connected(network, TRUE);
                break;
 
@@ -703,6 +756,9 @@ static void interface_removed(GSupplicantInterface *interface)
 
        wifi = g_supplicant_interface_get_data(interface);
 
+       if (wifi != NULL && wifi->tethering == TRUE)
+               return;
+
        if (wifi == NULL || wifi->device == NULL) {
                connman_error("Wrong wifi pointer");
                return;
@@ -713,26 +769,12 @@ static void interface_removed(GSupplicantInterface *interface)
 
 static void scan_started(GSupplicantInterface *interface)
 {
-       struct wifi_data *wifi;
-
        DBG("");
-
-       wifi = g_supplicant_interface_get_data(interface);
-
-       if (wifi == NULL)
-               return;
 }
 
 static void scan_finished(GSupplicantInterface *interface)
 {
-       struct wifi_data *wifi;
-
        DBG("");
-
-       wifi = g_supplicant_interface_get_data(interface);
-
-       if (wifi == NULL)
-               return;
 }
 
 static unsigned char calculate_strength(GSupplicantNetwork *supplicant_network)
@@ -751,7 +793,7 @@ static void network_added(GSupplicantNetwork *supplicant_network)
        struct connman_network *network;
        GSupplicantInterface *interface;
        struct wifi_data *wifi;
-       const char *name, *identifier, *mode, *security, *group;
+       const char *name, *identifier, *security, *group;
        const unsigned char *ssid;
        unsigned int ssid_len;
        connman_bool_t wps;
@@ -762,7 +804,6 @@ static void network_added(GSupplicantNetwork *supplicant_network)
        wifi = g_supplicant_interface_get_data(interface);
        name = g_supplicant_network_get_name(supplicant_network);
        identifier = g_supplicant_network_get_identifier(supplicant_network);
-       mode = g_supplicant_network_get_mode(supplicant_network);
        security = g_supplicant_network_get_security(supplicant_network);
        group = g_supplicant_network_get_identifier(supplicant_network);
        wps = g_supplicant_network_get_wps(supplicant_network);
@@ -786,6 +827,8 @@ static void network_added(GSupplicantNetwork *supplicant_network)
                        connman_network_unref(network);
                        return;
                }
+
+               wifi->networks = g_slist_append(wifi->networks, network);
        }
 
        if (name != NULL && name[0] != '\0')
@@ -793,12 +836,14 @@ static void network_added(GSupplicantNetwork *supplicant_network)
 
        connman_network_set_blob(network, "WiFi.SSID",
                                                ssid, ssid_len);
-       connman_network_set_string(network, "WiFi.Mode", mode);
        connman_network_set_string(network, "WiFi.Security", security);
        connman_network_set_strength(network,
                                calculate_strength(supplicant_network));
        connman_network_set_bool(network, "WiFi.WPS", wps);
 
+       connman_network_set_frequency(network,
+                       g_supplicant_network_get_frequency(supplicant_network));
+
        connman_network_set_available(network, TRUE);
 
        if (ssid != NULL)
@@ -810,6 +855,34 @@ static void network_removed(GSupplicantNetwork *network)
        GSupplicantInterface *interface;
        struct wifi_data *wifi;
        const char *name, *identifier;
+       struct connman_network *connman_network;
+
+       interface = g_supplicant_network_get_interface(network);
+       wifi = g_supplicant_interface_get_data(interface);
+       identifier = g_supplicant_network_get_identifier(network);
+       name = g_supplicant_network_get_name(network);
+
+       DBG("name %s", name);
+
+       if (wifi == NULL)
+               return;
+
+       connman_network = connman_device_get_network(wifi->device, identifier);
+       if (connman_network == NULL)
+               return;
+
+       wifi->networks = g_slist_remove(wifi->networks, connman_network);
+
+       connman_device_remove_network(wifi->device, connman_network);
+       connman_network_unref(connman_network);
+}
+
+static void network_changed(GSupplicantNetwork *network, const char *property)
+{
+       GSupplicantInterface *interface;
+       struct wifi_data *wifi;
+       const char *name, *identifier;
+       struct connman_network *connman_network;
 
        interface = g_supplicant_network_get_interface(network);
        wifi = g_supplicant_interface_get_data(interface);
@@ -818,8 +891,18 @@ static void network_removed(GSupplicantNetwork *network)
 
        DBG("name %s", name);
 
-       if (wifi != NULL)
-               connman_device_remove_network(wifi->device, identifier);
+       if (wifi == NULL)
+               return;
+
+       connman_network = connman_device_get_network(wifi->device, identifier);
+       if (connman_network == NULL)
+               return;
+
+       if (g_str_equal(property, "Signal") == TRUE) {
+              connman_network_set_strength(connman_network,
+                                       calculate_strength(network));
+              connman_network_update(connman_network);
+       }
 }
 
 static void debug(const char *str)
@@ -838,6 +921,7 @@ static const GSupplicantCallbacks callbacks = {
        .scan_finished          = scan_finished,
        .network_added          = network_added,
        .network_removed        = network_removed,
+       .network_changed        = network_changed,
        .debug                  = debug,
 };
 
@@ -854,6 +938,192 @@ static void tech_remove(struct connman_technology *technology)
        wifi_technology = NULL;
 }
 
+struct wifi_tethering_info {
+       struct wifi_data *wifi;
+       struct connman_technology *technology;
+       char *ifname;
+       GSupplicantSSID *ssid;
+};
+
+static GSupplicantSSID *ssid_ap_init(const char *ssid, const char *passphrase)
+{
+       GSupplicantSSID *ap;
+
+       ap = g_try_malloc0(sizeof(GSupplicantSSID));
+       if (ap == NULL)
+               return NULL;
+
+       ap->mode = G_SUPPLICANT_MODE_MASTER;
+       ap->ssid = ssid;
+       ap->ssid_len = strlen(ssid);
+       ap->scan_ssid = 0;
+       ap->freq = 2412;
+
+       if (passphrase == NULL || strlen(passphrase) == 0) {
+               ap->security = G_SUPPLICANT_SECURITY_NONE;
+               ap->passphrase = NULL;
+       } else {
+              ap->security = G_SUPPLICANT_SECURITY_PSK;
+              ap->protocol = G_SUPPLICANT_PROTO_RSN;
+              ap->pairwise_cipher = G_SUPPLICANT_PAIRWISE_CCMP;
+              ap->group_cipher = G_SUPPLICANT_GROUP_CCMP;
+              ap->passphrase = passphrase;
+       }
+
+       return ap;
+}
+
+static void ap_start_callback(int result, GSupplicantInterface *interface,
+                                                       void *user_data)
+{
+       struct wifi_tethering_info *info = user_data;
+
+       DBG("result %d index %d bridge %s",
+               result, info->wifi->index, info->wifi->bridge);
+
+       if (result < 0) {
+               connman_inet_remove_from_bridge(info->wifi->index,
+                                                       info->wifi->bridge);
+               connman_technology_tethering_notify(info->technology, FALSE);
+       }
+
+       g_free(info->ifname);
+       g_free(info);
+}
+
+static void ap_create_callback(int result,
+                               GSupplicantInterface *interface,
+                                       void *user_data)
+{
+       struct wifi_tethering_info *info = user_data;
+
+       DBG("result %d ifname %s", result,
+                               g_supplicant_interface_get_ifname(interface));
+
+       if (result < 0) {
+               connman_inet_remove_from_bridge(info->wifi->index,
+                                                       info->wifi->bridge);
+               connman_technology_tethering_notify(info->technology, FALSE);
+
+               g_free(info->ifname);
+               g_free(info);
+               return;
+       }
+
+       info->wifi->interface = interface;
+       g_supplicant_interface_set_data(interface, info->wifi);
+
+       if (g_supplicant_interface_set_apscan(interface, 2) < 0)
+               connman_error("Failed to set interface ap_scan property");
+
+       g_supplicant_interface_connect(interface, info->ssid,
+                                               ap_start_callback, info);
+}
+
+static void sta_remove_callback(int result,
+                               GSupplicantInterface *interface,
+                                       void *user_data)
+{
+       struct wifi_tethering_info *info = user_data;
+       const char *driver = connman_option_get_string("wifi");
+
+       DBG("ifname %s result %d ", info->ifname, result);
+
+       if (result < 0) {
+               info->wifi->tethering = TRUE;
+
+               g_free(info->ifname);
+               g_free(info);
+               return;
+       }
+
+       info->wifi->interface = NULL;
+
+       connman_technology_tethering_notify(info->technology, TRUE);
+
+       g_supplicant_interface_create(info->ifname, driver, info->wifi->bridge,
+                                               ap_create_callback,
+                                                       info);
+}
+
+static int tech_set_tethering(struct connman_technology *technology,
+                               const char *identifier, const char *passphrase,
+                               const char *bridge, connman_bool_t enabled)
+{
+       GList *list;
+       GSupplicantInterface *interface;
+       struct wifi_data *wifi;
+       struct wifi_tethering_info *info;
+       const char *ifname;
+       unsigned int mode;
+       int err;
+
+       DBG("");
+
+       if (enabled == FALSE) {
+               for (list = iface_list; list; list = list->next) {
+                       wifi = list->data;
+
+                       if (wifi->tethering == TRUE) {
+                               wifi->tethering = FALSE;
+
+                               connman_inet_remove_from_bridge(wifi->index,
+                                                                       bridge);
+                               wifi->bridged = FALSE;
+                       }
+               }
+
+               connman_technology_tethering_notify(technology, FALSE);
+
+               return 0;
+       }
+
+       for (list = iface_list; list; list = list->next) {
+               wifi = list->data;
+
+               interface = wifi->interface;
+
+               if (interface == NULL)
+                       continue;
+
+               ifname = g_supplicant_interface_get_ifname(wifi->interface);
+
+               mode = g_supplicant_interface_get_mode(interface);
+               if ((mode & G_SUPPLICANT_CAPABILITY_MODE_AP) == 0) {
+                       DBG("%s does not support AP mode", ifname);
+                       continue;
+               }
+
+               info = g_try_malloc0(sizeof(struct wifi_tethering_info));
+               if (info == NULL)
+                       return -ENOMEM;
+
+               info->wifi = wifi;
+               info->technology = technology;
+               info->wifi->bridge = bridge;
+               info->ssid = ssid_ap_init(identifier, passphrase);
+               if (info->ssid == NULL) {
+                       g_free(info);
+                       continue;
+               }
+               info->ifname = g_strdup(ifname);
+               if (info->ifname == NULL) {
+                       g_free(info);
+                       continue;
+               }
+
+               info->wifi->tethering = TRUE;
+
+               err = g_supplicant_interface_remove(interface,
+                                               sta_remove_callback,
+                                                       info);
+               if (err == 0)
+                       return err;
+       }
+
+       return -EOPNOTSUPP;
+}
+
 static void regdom_callback(void *user_data)
 {
        char *alpha2 = user_data;
@@ -876,6 +1146,7 @@ static struct connman_technology_driver tech_driver = {
        .type           = CONNMAN_SERVICE_TYPE_WIFI,
        .probe          = tech_probe,
        .remove         = tech_remove,
+       .set_tethering  = tech_set_tethering,
        .set_regdom     = tech_set_regdom,
 };