D-Bus tutorial in C to communicate with wpa_supplicant

我们两清 提交于 2019-11-28 20:37:44

You have given up the tools that would help you to learn D-Bus more easily and are using the low level libdbus implementation, so maybe you deserve to be in pain. BTW, are you talking about ARM, like a cell phone ARM ? With maybe 500 Mhz and 256 MB RAM ? In this case the processor is well suited to using glib, Qt or even python. And D-Bus is most useful when you're writing asynchronous event driven code, with an integrated main loop, for example from glib, even when you're using the low level libdbus (it has functions to connect to the glib main loop, for example).

Since you're using the low level library, then documentation is what you already have:

http://dbus.freedesktop.org/doc/api/html/index.html

Also, libdbus source code is also part of the documentation:

http://dbus.freedesktop.org/doc/api/html/files.html

The main entry point for the documentation is the Modules page (in particular, the public API section):

http://dbus.freedesktop.org/doc/api/html/modules.html

For message handling, the section DBusMessage is the relevant one: DBusMessage

There you have the documentation for functions that parse item values. In your case, you started with a dbus_message_iter_get_basic. As described in the docs, retrieving the string requires a const char ** variable, since the returned value will point to the pre-allocated string in the received message:

So for int32 it should be a "dbus_int32_t*" and for string a "const char**". The returned value is by reference and should not be freed.

So you can't define an array, because libdbus won't copy the text to your array. If you need to save the string, first get the constant string reference, then strcpy to your own array.

Then you tried to get a fixed array without moving the iterator. You need a call to the next iterator (dbus_message_iter_next) between the basic string and the fixed array. Same right before recursing into the sub iterator.

Finally, you don't call get_array_len to get the number of elements on the array. From the docs, it only returns byte counts. Instead you loop over the sub iterator using iter_next the same way you should have done with the main iterator. After you have iterated past the end of the array, dbus_message_iter_get_arg_type will return DBUS_TYPE_INVALID.

For more info, read the reference manual, don't look for a tutorial. Or just use a reasonable d-bus implementation:

https://developer.gnome.org/gio/2.36/gdbus-codegen.html

GIO's GDBus automatically creates wrappers for your d-bus calls.

http://qt-project.org/doc/qt-4.8/intro-to-dbus.html

http://dbus.freedesktop.org/doc/dbus-python/doc/tutorial.html

etc.

You don't need to use/understand working of dbus If you just need to write a C program to communicate with wpa_supplicant. I reverse engineered the wpa_cli's source code. Went through its implementation and used functions provided in wpa_ctrl.h/c. This implementation takes care of everything. You can use/modify whatever you want, build your executable and you're done!

Here's the official link to wpa_supplicant's ctrl_interface: http://hostap.epitest.fi/wpa_supplicant/devel/ctrl_iface_page.html

The below snippet works for me

if (argType == DBUS_TYPE_STRING)
{
printf("Got string argument, extracting ...\n");
char* strBuffer = NULL;
dbus_message_iter_get_basic(&args, &strBuffer);
printf("Received string: \n %s \n",strBuffer);

} 

I doubt this answer will still be relevant to the author of this question, but for anybody who stumbles upon this like I did:

The situation is now better than all those years ago if you don't want to include GTK/QT in your project to access dbus. There is dbus API in Embedded Linux Library by Intel (weird I remember it being open, maybe it is just for registered users now?) and systemd sd-bus library now offers public API. You probably run systemd anyway unless you have a really constrained embedded system.

I have worked with GDbus, dbus-cpp and sd-bus and although I wanted a C++ library, I found sd-bus to be the simplest and the least problematic experience. I did not try its C++ bindings but they also look nice

#include <stdio.h>
#include <systemd/sd-bus.h>
#include <stdlib.h>

const char* wpa_service = "fi.w1.wpa_supplicant1";
const char* wpa_root_obj_path = "/fi/w1/wpa_supplicant1";
const char* wpa_root_iface = "fi.w1.wpa_supplicant1";

sd_bus_error error = SD_BUS_ERROR_NULL;
sd_bus* system_bus = NULL;
sd_event* loop = NULL;
sd_bus_message* reply = NULL;

void cleanup() {
    sd_event_unref(loop);
    sd_bus_unref(system_bus);
    sd_bus_message_unref(reply);
    sd_bus_error_free(&error);
}

void print_error(const char* msg, int code) {
    fprintf(stderr, "%s %s\n", msg, strerror(-code));
    exit(EXIT_FAILURE);
}

const char* get_interface(const char* iface) {
    int res = sd_bus_call_method(system_bus,
                               wpa_service,
                               wpa_root_obj_path,
                               wpa_root_iface,
                               "GetInterface",
                               &error,
                               &reply,
                               "s",
                               "Ifname", "s", iface,
                               "Driver", "s", "nl80211");
    if (res < 0) {
        fprintf(stderr, "(get) error response: %s\n", error.message);
        return NULL;
    }

    const char* iface_path;
    /*
     * an object path was returned in reply
     * this works like an iterator, if a method returns (osu), you could call message_read_basic in succession
     * with arguments SD_BUS_TYPE_OBJECT_PATH, SD_BUS_TYPE_STRING, SD_BUS_TYPE_UINT32 or you could
     * call sd_bus_message_read() and provides the signature + arguments in one call
     * */
    res = sd_bus_message_read_basic(reply, SD_BUS_TYPE_OBJECT_PATH, &iface_path);
    if (res < 0) {
        print_error("getIface: ", res);
        return NULL;
    }
    return iface_path;
}

const char* create_interface(const char* iface) {
    int res = sd_bus_call_method(system_bus,
                               wpa_service,
                               wpa_root_obj_path,
                               wpa_root_iface,
                               "CreateInterface",
                               &error,
                               &reply,
                               "a{sv}", 2, //pass array of str:variant (dbus dictionary) with 2
                                            //entries to CreateInterface
                               "Ifname", "s", iface, // "s" variant parameter contains string, then pass the value
                               "Driver", "s", "nl80211");
    if (res < 0) {
        fprintf(stderr, "(create) error response: %s\n", error.message);
        return NULL;
    }
    const char* iface_path;
    res = sd_bus_message_read_basic(reply, SD_BUS_TYPE_OBJECT_PATH, &iface_path);
    if (res < 0) {
        print_error("createIface: ", res);
    }
    return iface_path;
}

int main() {
    int res;
    const char* iface_path;

    //open connection to system bus - default either opens or reuses existing connection as necessary
    res = sd_bus_default_system(&system_bus);
    if (res < 0) {
        print_error("open: ", res);
    }

    //associate connection with event loop, again default either creates or reuses existing
    res = sd_event_default(&loop);
    if (res < 0) {
        print_error("event: ", res);
    }

    // get obj. path to the wireless interface on dbus so you can call methods on it
    // this is a wireless interface (e.g. your wifi dongle) NOT the dbus interface
    // if you don't know the interface name in advance, you will have to read the Interfaces property of
    // wpa_supplicants root interface — call Get method on org.freedesktop.DBus properties interface,
    // while some libraries expose some kind of get_property convenience function sd-bus does not
    const char* ifaceName = "wlp32s0f3u2";
    if (!(iface_path = get_interface(ifaceName))) { //substitute your wireless iface here
        // sometimes the HW is present and listed in "ip l" but dbus does not reflect that, this fixes it
        if (!(iface_path = create_interface(ifaceName))) {
            fprintf(stderr, "can't create iface: %s" , ifaceName);
            cleanup();
            return EXIT_FAILURE;
        }
    }

    /*
    call methods with obj. path iface_path and dbus interface of your choice
    this will likely be "fi.w1.wpa_supplicant1.Interface", register for signals etc...
    you will need the following to receive those signals
    */
    int runForUsec = 1000000; //usec, not msec!
    sd_event_run(loop, runForUsec); //or sd_event_loop(loop) if you want to loop forever

    cleanup();
    printf("Finished OK\n");
    return 0;
}

I apologize if the example above does not work perfectly. It is an excerpt from an old project I rewrote to C from C++ (I think it's C(-ish), compiler does not protest and you asked for C) but I can't test it as all my dongles refuse to work with my desktop right now. It should give you a general idea though.

Note that you will likely encounter several magical or semi-magical issues. To ensure smooth developing/testing do the following:

  1. make sure other network management applications are disabled (networkmanager, connman...)
  2. restart the wpa_supplicant service
  3. make sure the wireless interface is UP in ip link

Also, because is not that well-documented right now: You can access arrays and inner variant values by sd_bus_message_enter_container and _exit counterpart. sd_bus_message_peek_type might come handy while doing that. Or sd_bus_message_read_array for a homogenous array.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!