From 696510db8f43427a9bb6ff249f688851f531cd12 Mon Sep 17 00:00:00 2001 From: Gatis Paeglis Date: Thu, 2 Jan 2014 15:57:14 +0100 Subject: Fix dhcp issues and improve public API - Use qconnectivity daemon for dhcp requests. Since dhcp requests are executed in other process we don't block gui thread for this lengthy operation. - Why not to use "do_dhcp_request" - it is a legacy implementation of a dhcp client and is not used anywhere in the Android source code itself. It appears that do_dhcp_request was not removed from the libhardware_legacy to keep compatibility for others, whose code might still depend on it. - Differnet changes to the internal implementation and the public API. - Add 'Interface' singleton type, installing a singleton type allows developers to provide arbitrary functionality to a client without requiring individual instances of the type to be instantiated by the client. We use this to determine if Android has wifi interface. Change-Id: I836f3a2a587b1ebf9f670ed08b10fe3483504b9e Reviewed-by: Eirik Aavitsland --- src/imports/wifi/pluginmain.cpp | 35 +++- src/imports/wifi/qwifimanager.cpp | 328 ++++++++++++++++++++------------------ src/imports/wifi/qwifimanager.h | 65 ++++---- src/imports/wifi/qwifimodule.pro | 21 --- src/imports/wifi/wifi.pro | 21 +++ 5 files changed, 265 insertions(+), 205 deletions(-) delete mode 100644 src/imports/wifi/qwifimodule.pro create mode 100644 src/imports/wifi/wifi.pro (limited to 'src/imports/wifi') diff --git a/src/imports/wifi/pluginmain.cpp b/src/imports/wifi/pluginmain.cpp index 58aa590..0eeff78 100644 --- a/src/imports/wifi/pluginmain.cpp +++ b/src/imports/wifi/pluginmain.cpp @@ -21,6 +21,36 @@ #include #include +#include +#include + +class QWifiGlobal : public QObject +{ + Q_OBJECT +public: + explicit QWifiGlobal(QObject *parent = 0) + : QObject(parent) {} + ~QWifiGlobal() {} + + Q_INVOKABLE bool wifiSupported() const + { + char interface[PROPERTY_VALUE_MAX]; + property_get("wifi.interface", interface, NULL); + // standard linux kernel path + QByteArray path; + path.append("/sys/class/net/").append(interface); + bool interfaceFound = QDir().exists(path.constData()); + if (!interfaceFound) + qWarning() << "QWifiGlobal: could not find wifi interface in " << path; + return interfaceFound; + } +}; + +static QObject *global_object_wifi(QQmlEngine *, QJSEngine *) +{ + return new QWifiGlobal; +} + class QWifiPlugin : public QQmlExtensionPlugin { Q_OBJECT @@ -31,11 +61,10 @@ public: { Q_ASSERT(QLatin1String(uri) == QLatin1String("Qt.labs.wifi")); - qmlRegisterType(uri, 0, 1, "QWifiManager"); + qmlRegisterType(uri, 0, 1, "WifiManager"); qmlRegisterType(); + qmlRegisterSingletonType(uri, 0, 1, "Interface", global_object_wifi); } }; #include "pluginmain.moc" - - diff --git a/src/imports/wifi/qwifimanager.cpp b/src/imports/wifi/qwifimanager.cpp index b2b9363..e5ed67e 100644 --- a/src/imports/wifi/qwifimanager.cpp +++ b/src/imports/wifi/qwifimanager.cpp @@ -21,54 +21,48 @@ #include #include -#include +#include #include -#define WLAN_INTERFACE "wlan0" +static const char SUPPLICANT_SVC[] = "init.svc.wpa_supplicant"; +static const char WIFI_INTERFACE[] = "wifi.interface"; +static const char QT_WIFI_BACKEND[] = "qt.wifi"; static bool QT_WIFI_DEBUG = !qgetenv("QT_WIFI_DEBUG").isEmpty(); const QEvent::Type WIFI_SCAN_RESULTS = (QEvent::Type) (QEvent::User + 2001); const QEvent::Type WIFI_CONNECTED = (QEvent::Type) (QEvent::User + 2002); -static int q_wifi_start_supplicant() +/* + * This function is borrowed from /system/core/libnetutils/dhcp_utils.c + * + * Wait for a system property to be assigned a specified value. + * If desired_value is NULL, then just wait for the property to + * be created with any value. maxwait is the maximum amount of + * time in seconds to wait before giving up. + */ +static const int NAP_TIME = 200; // wait for 200ms at a time when polling for property values +static int wait_for_property(const char *name, const char *desired_value, int maxwait) { -#if Q_ANDROID_VERSION_MAJOR > 4 || (Q_ANDROID_VERSION_MAJOR == 4 && Q_ANDROID_VERSION_MINOR >= 1) - return wifi_start_supplicant(0); -#else - return wifi_start_supplicant(); -#endif -} - -static int q_wifi_connect_to_supplicant() -{ -#if Q_ANDROID_VERSION_MAJOR > 4 || (Q_ANDROID_VERSION_MAJOR == 4 && Q_ANDROID_VERSION_MINOR >= 1) - return wifi_connect_to_supplicant(WLAN_INTERFACE); -#else - return wifi_connect_to_supplicant(); -#endif -} - -static int q_wifi_command(const char *command, char *reply, size_t *reply_len) -{ -#if Q_ANDROID_VERSION_MAJOR > 4 || (Q_ANDROID_VERSION_MAJOR == 4 && Q_ANDROID_VERSION_MINOR >= 1) - return wifi_command(WLAN_INTERFACE, command, reply, reply_len); -#else - return wifi_command(command, reply, reply_len); -#endif + char value[PROPERTY_VALUE_MAX] = {'\0'}; + int maxnaps = (maxwait * 1000) / NAP_TIME; -} + if (maxnaps < 1) { + maxnaps = 1; + } -static int q_wifi_wait_for_event(char *buf, size_t len) -{ -#if Q_ANDROID_VERSION_MAJOR > 4 || (Q_ANDROID_VERSION_MAJOR == 4 && Q_ANDROID_VERSION_MINOR >= 1) - return wifi_wait_for_event(WLAN_INTERFACE, buf, len); -#else - return wifi_wait_for_event(buf, len); -#endif + while (maxnaps-- > 0) { + usleep(NAP_TIME * 1000); + if (property_get(name, value, NULL)) { + if (desired_value == NULL || + strcmp(value, desired_value) == 0) { + return 0; + } + } + } + return -1; /* failure */ } - class QWifiManagerEvent : public QEvent { public: @@ -84,13 +78,12 @@ private: QByteArray m_data; }; - - class QWifiManagerEventThread : public QThread { public: - QWifiManagerEventThread(QWifiManager *manager) + QWifiManagerEventThread(QWifiManager *manager, const QByteArray &interface) : m_manager(manager) + , m_if(interface) { } @@ -99,7 +92,9 @@ public: if (QT_WIFI_DEBUG) qDebug("EventReceiver thread is running"); char buffer[2048]; while (1) { - int size = q_wifi_wait_for_event(buffer, sizeof(buffer) - 1); + if (m_manager->exiting()) + return; + int size = wifi_wait_for_event(m_if.constData(), buffer, sizeof(buffer) - 1); if (size > 0) { buffer[size] = 0; @@ -112,34 +107,149 @@ public: } else if (strstr(event, "CONNECTED")) { QWifiManagerEvent *e = new QWifiManagerEvent(WIFI_CONNECTED); QCoreApplication::postEvent(m_manager, e); + } else if (strstr(event, "TERMINATING")) { + // stop monitoring for events when supplicant is terminating + return; } } } } QWifiManager *m_manager; + QByteArray m_if; }; - - QWifiManager::QWifiManager() : m_networks(this) , m_eventThread(0) , m_scanTimer(0) - , m_internalState(IS_Uninitialized) , m_scanning(false) + , m_daemonClientSocket(0) + , m_exiting(false) +{ + char interface[PROPERTY_VALUE_MAX]; + property_get(WIFI_INTERFACE, interface, NULL); + m_interface = interface; + if (QT_WIFI_DEBUG) qDebug("QWifiManager: using wifi interface: %s", m_interface.constData()); + m_eventThread = new QWifiManagerEventThread(this, m_interface); + + m_daemonClientSocket = new QLocalSocket; + int qconnFd = socket_local_client("qconnectivity", ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_STREAM); + if (qconnFd != -1) { + m_daemonClientSocket->setSocketDescriptor(qconnFd); + QObject::connect(m_daemonClientSocket, SIGNAL(readyRead()), this, SLOT(handleDhcpReply())); + QObject::connect(m_daemonClientSocket, SIGNAL(connected()), this, SLOT(connectedToDaemon())); + } else { + qWarning() << "QWifiManager: failed to connect to qconnectivity socket"; + } + + // check if backend has already been initialized + char backend_status[PROPERTY_VALUE_MAX]; + if (property_get(QT_WIFI_BACKEND, backend_status, NULL) + && strcmp(backend_status, "running") == 0) { + // let it re-connect, in most cases this will see that everything is working properly + // and will do nothing. Special case is when process has crashed or was killed by a system + // signal in previous execution, which results in broken connection to a supplicant, + // connectToBackend will fix it.. + connectToBackend(); + } else { + // same here, cleans up the state + disconnectFromBackend(); + } +} + +QWifiManager::~QWifiManager() +{ + m_exiting = true; + m_eventThread->wait(); +} + +void QWifiManager::handleDhcpReply() { + if (m_daemonClientSocket->canReadLine()) { + QByteArray receivedMessage; + receivedMessage = m_daemonClientSocket->readLine(m_daemonClientSocket->bytesAvailable()); + if (QT_WIFI_DEBUG) qDebug() << "QWifiManager: reply from qconnectivity: " << receivedMessage; + if (receivedMessage == "success") { + m_state = Connected; + emit networkStateChanged(); + emit connectedSSIDChanged(m_connectedSSID); + // Store settings of a working wifi connection + call("SAVE_CONFIG"); + } else if (receivedMessage == "failed") { + m_state = DhcpRequestFailed; + emit networkStateChanged(); + } else { + qWarning() << "QWifiManager: unknown message: " << receivedMessage; + } + } } +void QWifiManager::sendDhcpRequest(const QByteArray &request) +{ + if (QT_WIFI_DEBUG) qDebug() << "QWifiManager: sending request - " << request; + m_request = request; + m_request.append("\n"); + m_daemonClientSocket->abort(); + // path where android stores "reserved" sockets + m_daemonClientSocket->connectToServer(ANDROID_SOCKET_DIR "/qconnectivity"); +} +void QWifiManager::connectedToDaemon() +{ + m_daemonClientSocket->write(m_request.constData(), m_request.length()); + m_daemonClientSocket->flush(); +} void QWifiManager::start() { - if (QT_WIFI_DEBUG) qDebug("QWifiManager: start"); + if (QT_WIFI_DEBUG) qDebug("QWifiManager: connecting to the backend"); connectToBackend(); } +void QWifiManager::stop() +{ + if (QT_WIFI_DEBUG) qDebug("QWifiManager: shutting down"); + disconnectFromBackend(); +} + +void QWifiManager::connectToBackend() +{ + if (!(is_wifi_driver_loaded() || wifi_load_driver() == 0)) { + qWarning("QWifiManager: failed to load a driver"); + return; + } + if (wifi_start_supplicant(0) != 0) { + qWarning("QWifiManager: failed to start a supplicant"); + return; + } + if (wait_for_property(SUPPLICANT_SVC, "running", 5) < 0) { + qWarning("QWifiManager: Timed out waiting for supplicant to start"); + return; + } + if (wifi_connect_to_supplicant(m_interface.constData()) == 0) { + m_backendReady = true; + emit backendReadyChanged(); + property_set(QT_WIFI_BACKEND, "running"); + } else { + qWarning("QWifiManager: failed to connect to a supplicant"); + return; + } + if (QT_WIFI_DEBUG) qDebug("QWifiManager: started successfully"); + m_eventThread->start(); + handleConnected(); +} +void QWifiManager::disconnectFromBackend() +{ + m_eventThread->quit(); + if (wifi_stop_supplicant(0) < 0) + qWarning("QWifiManager: failed to stop supplicant"); + wifi_close_supplicant_connection(m_interface.constData()); + property_set(QT_WIFI_BACKEND, "stopped"); + m_backendReady = false; + emit backendReadyChanged(); +} void QWifiManager::setScanning(bool scanning) { @@ -147,70 +257,23 @@ void QWifiManager::setScanning(bool scanning) return; m_scanning = scanning; - emit scanningChanged(scanning); + emit scanningChanged(m_scanning); if (m_scanning) { if (QT_WIFI_DEBUG) qDebug("QWifiManager: scanning"); call("SCAN"); - m_scanTimer = startTimer(5000); + m_scanTimer = startTimer(5000); // ### todo - this could be a qml property } else { if (QT_WIFI_DEBUG) qDebug("QWifiManager: stop scanning"); killTimer(m_scanTimer); } } - - -QByteArray int_to_ip(int i) { - QByteArray ip; - - ip.append(QByteArray::number(i & 0x000000ff)); - ip.append('.'); - ip.append(QByteArray::number((i & 0x0000ff00) >> 8)); - ip.append('.'); - ip.append(QByteArray::number((i & 0x00ff0000) >> 16)); - ip.append('.'); - ip.append(QByteArray::number(i >> 24)); - - return ip; -} - - -void QWifiManager::connectToBackend() -{ - if (m_internalState == IS_Uninitialized) - m_internalState = IS_LoadDriver; - - if (m_internalState == IS_LoadDriver && (is_wifi_driver_loaded() || wifi_load_driver() >= 0)) - m_internalState = IS_StartBackend; - - if (m_internalState == IS_StartBackend && q_wifi_start_supplicant() >= 0) { - m_internalState = IS_ConnectToBackend; - sleep(3); //### - } - - if (m_internalState == IS_ConnectToBackend && q_wifi_connect_to_supplicant() >= 0) - m_internalState = IS_UpAndRunning; - - if (m_internalState == IS_UpAndRunning) { - qDebug("QWifiManager: started successfully"); - - emit readyChanged(true); - m_eventThread = new QWifiManagerEventThread(this); - m_eventThread->start(); - - handleConnected(); - } else { - qWarning("QWifiManager: stuck at internal state level: %d", m_internalState); - } -} - - -QByteArray QWifiManager::call(const char *command) +QByteArray QWifiManager::call(const char *command) const { char data[2048]; size_t len = sizeof(data) - 1; // -1: room to add a 0-terminator - if (q_wifi_command(command, data, &len) < 0) { + if (wifi_command(m_interface.constData(), command, data, &len) < 0) { qWarning("QWifiManager: call failed: %s", command); return QByteArray(); } @@ -221,13 +284,11 @@ QByteArray QWifiManager::call(const char *command) return result; } - -bool QWifiManager::checkedCall(const char *command) +bool QWifiManager::checkedCall(const char *command) const { return call(command).trimmed().toUpper() == "OK"; } - bool QWifiManager::event(QEvent *e) { switch ((int) e->type()) { @@ -261,9 +322,10 @@ void QWifiManager::connect(QWifiNetwork *network, const QString &passphrase) if (!m_connectedSSID.isEmpty()) { m_connectedSSID.clear(); emit connectedSSIDChanged(m_connectedSSID); - //also possibly change online state } + m_state = ObtainingIPAddress; + emit networkStateChanged(); bool networkKnown = false; QByteArray id; QByteArray listResult = call("LIST_NETWORKS"); @@ -295,7 +357,7 @@ void QWifiManager::connect(QWifiNetwork *network, const QString &passphrase) QByteArray key_mgmt; if (network->supportsWPA() || network->supportsWPA2()) { ok = ok && checkedCall(setNetworkCommand + QByteArray(" psk ") + '"' + passphrase.toLatin1() + '"'); - key_mgmt = "WPA_PSK"; + key_mgmt = "WPA-PSK"; } else if (network->supportsWEP()) { ok = ok && checkedCall(setNetworkCommand + QByteArray(" wep_key0 ") + '"' + passphrase.toLatin1() + '"'); ok = ok && checkedCall(setNetworkCommand + QByteArray(" auth_alg OPEN SHARED")); @@ -317,19 +379,16 @@ void QWifiManager::connect(QWifiNetwork *network, const QString &passphrase) call("RECONNECT"); } - -class ProcessRunner : public QThread { -public: - void run() { - QStringList args; - args << QStringLiteral("-A") - << QStringLiteral("-h") << QStringLiteral("KAON") //### hardcoded hostname, for testing - << QStringLiteral("wlan0"); - QProcess::execute(QStringLiteral("dhcpcd"), args); - deleteLater(); - } -}; - +void QWifiManager::disconnect() +{ + call("DISCONNECT"); + QByteArray req = m_interface; + sendDhcpRequest(req.append(" disconnect")); + m_state = Disconnected; + m_connectedSSID.clear(); + emit networkStateChanged(); + emit connectedSSIDChanged(m_connectedSSID); +} void QWifiManager::handleConnected() { @@ -344,49 +403,16 @@ void QWifiManager::handleConnected() if (connectedNetwork.isEmpty()) { if (QT_WIFI_DEBUG) qDebug("QWifiManager::handleConnected: not connected to a network..."); - m_online = false; - onlineChanged(m_online); + m_state = Disconnected; + emit networkStateChanged(); return; - } else { - if (QT_WIFI_DEBUG) qDebug("QWifiManager::handleConnected: current is %s", connectedNetwork.constData()); } + if (QT_WIFI_DEBUG) qDebug("QWifiManager::handleConnected: current is %s", connectedNetwork.constData()); + QList info = connectedNetwork.split('\t'); m_connectedSSID = info.at(1); - emit connectedSSIDChanged(m_connectedSSID); - - if (QT_WIFI_DEBUG) qDebug("QWifiManager::handleConnected: starting dhcpcd..."); - QThread *t = new ProcessRunner(); - t->start(); - - int ipaddr, gateway, mask, dns1, dns2, server, lease; - if (do_dhcp_request(&ipaddr, &gateway, &mask, &dns1, &dns2, &server, &lease) == 0) { - if (QT_WIFI_DEBUG) { - printf("ip ........: %s\n" - "gateway ...: %s\n" - "mask ......: %s\n" - "dns1 ......: %s\n" - "dns2 ......: %s\n" - "lease .....: %d\n", - int_to_ip(ipaddr).constData(), - int_to_ip(gateway).constData(), - int_to_ip(mask).constData(), - int_to_ip(dns1).constData(), - int_to_ip(dns2).constData(), - lease); - } - - // Updating dns values.. - property_set("net.dns1", int_to_ip(dns1).constData()); - property_set("net.dns2", int_to_ip(dns2).constData()); - - // Store (possibly updated) settings - call("SAVE_CONFIG"); - - m_online = true; - onlineChanged(m_online); - } else { - if (QT_WIFI_DEBUG) qDebug("QWifiManager::handleConnected: dhcp request failed..."); - } + QByteArray req = m_interface; + sendDhcpRequest(req.append(" connect")); } diff --git a/src/imports/wifi/qwifimanager.h b/src/imports/wifi/qwifimanager.h index 59f2b44..85384cc 100644 --- a/src/imports/wifi/qwifimanager.h +++ b/src/imports/wifi/qwifimanager.h @@ -21,6 +21,9 @@ #include #include +#include + +#include #include "qwifinetworklist.h" @@ -29,71 +32,73 @@ class QWifiManagerEventThread; class QWifiManager : public QObject { Q_OBJECT - - Q_PROPERTY(bool ready READ isReady NOTIFY readyChanged) - Q_PROPERTY(bool online READ isOnline NOTIFY onlineChanged) + Q_ENUMS(NetworkState) + Q_PROPERTY(NetworkState networkState READ networkState NOTIFY networkStateChanged) + Q_PROPERTY(bool backendReady READ isbackendReady NOTIFY backendReadyChanged) Q_PROPERTY(bool scanning READ scanning WRITE setScanning NOTIFY scanningChanged) - Q_PROPERTY(QString connectedSSID READ connectedSSID NOTIFY connectedSSIDChanged) Q_PROPERTY(QWifiNetworkList *networks READ networks CONSTANT) public: - enum InternalState { - IS_Uninitialized, - IS_LoadDriver, - IS_StartBackend, - IS_ConnectToBackend, - IS_UpAndRunning + enum NetworkState { + Disconnected, + ObtainingIPAddress, + DhcpRequestFailed, + Connected }; QWifiManager(); + ~QWifiManager(); QWifiNetworkList *networks() { return &m_networks; } - QString connectedSSID() const { return m_connectedSSID; } - bool scanning() const { return m_scanning; } void setScanning(bool scanning); - - bool isReady() const { return m_internalState == IS_UpAndRunning; } - bool isOnline() const { return m_online; } - + NetworkState networkState() const { return m_state; } + bool isbackendReady() const { return m_backendReady; } + bool exiting() const { return m_exiting; } public slots: void start(); - + void stop(); void connect(QWifiNetwork *network, const QString &passphrase); + void disconnect(); signals: void scanningChanged(bool arg); - void readyChanged(bool ready); - void onlineChanged(bool online); + void networkStateChanged(); + void backendReadyChanged(); void connectedSSIDChanged(const QString &); protected: bool event(QEvent *); + void sendDhcpRequest(const QByteArray &request); + void handleConnected(); + void connectToBackend(); + void disconnectFromBackend(); + QByteArray call(const char *command) const; + bool checkedCall(const char *command) const; + +protected slots: + void connectedToDaemon(); + void handleDhcpReply(); private: friend class QWifiManagerEventThread; - void handleConnected(); - void parseScanResults(); - void connectToBackend(); - QByteArray call(const char *command); - bool checkedCall(const char *command); - QString m_connectedSSID; QWifiNetworkList m_networks; - QWifiManagerEventThread *m_eventThread; int m_scanTimer; - - InternalState m_internalState; - bool m_scanning; - bool m_online; + bool m_backendReady; + QByteArray m_interface; + NetworkState m_state; + QLocalSocket *m_daemonClientSocket; + QByteArray m_request; + bool m_exiting; }; #endif // QWIFIMANAGER_H diff --git a/src/imports/wifi/qwifimodule.pro b/src/imports/wifi/qwifimodule.pro deleted file mode 100644 index 449617f..0000000 --- a/src/imports/wifi/qwifimodule.pro +++ /dev/null @@ -1,21 +0,0 @@ -CXX_MODULE = qml -QT += qml quick -TARGET = qwifimodule -TARGETPATH = Qt/labs/wifi -IMPORT_VERSION = 0.1 - -SOURCES += \ - pluginmain.cpp \ - qwifimanager.cpp \ - qwifinetwork.cpp \ - qwifinetworklist.cpp - -HEADERS += \ - qwifimanager.h \ - qwifinetwork.h \ - qwifinetworklist.h - -LIBS += -lhardware_legacy -lcutils - -load(qml_plugin) - diff --git a/src/imports/wifi/wifi.pro b/src/imports/wifi/wifi.pro new file mode 100644 index 0000000..0231479 --- /dev/null +++ b/src/imports/wifi/wifi.pro @@ -0,0 +1,21 @@ +CXX_MODULE = qml +QT += qml quick network +TARGET = qwifimodule +TARGETPATH = Qt/labs/wifi +IMPORT_VERSION = 0.1 + +SOURCES += \ + pluginmain.cpp \ + qwifimanager.cpp \ + qwifinetwork.cpp \ + qwifinetworklist.cpp + +HEADERS += \ + qwifimanager.h \ + qwifinetwork.h \ + qwifinetworklist.h + +LIBS += -lhardware_legacy -lcutils + +load(qml_plugin) + -- cgit v1.2.3