Linux C中的D-Bus教程与wpa_supplicant进行通信

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/8521661/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 03:40:18  来源:igfitidea点击:

D-Bus tutorial in C to communicate with wpa_supplicant

linuxembeddeddbus

提问by morandg

I'm trying to write some code to communicate with wpa_supplicant using DBUS. As I'm working in an embedded system (ARM), I'd like to avoid the use of Python or the GLib. I'm wondering if I'm stupid because I really have the feeling that there is no nice and clear documentation about D-Bus. Even with the official one, I either find the documentation too high level, or the examples shown are using Glib! Documentation I've looked at: http://www.freedesktop.org/wiki/Software/dbus

我正在尝试编写一些代码来使用 DBUS 与 wpa_supplicant 进行通信。由于我在嵌入式系统 (ARM) 中工作,因此我想避免使用 Python 或 GLib。我想知道我是不是傻,因为我真的觉得没有关于 D-Bus 的漂亮和清晰的文档。即使是官方的,我也觉得文档太高级了,或者显示的示例使用的是 Glib!我看过的文档:http: //www.freedesktop.org/wiki/Software/dbus

I found a nice article about using D-Bus in C: http://www.matthew.ath.cx/articles/dbus

我发现了一篇关于在 C 中使用 D-Bus 的好文章:http: //www.matthew.ath.cx/articles/dbus

However, this article is pretty old and not complete enough! I also found the c++-dbus API but also here, I don't find ANY documentation! I've been digging into wpa_supplicant and NetworkManager source code but it's quite a nightmare! I've been looking into the "low-level D-Bus API" as well but this doesn't tell me how to extract a string parameter from a D-Bus message! http://dbus.freedesktop.org/doc/api/html/index.html

然而,这篇文章已经很老了,不够完整!我还找到了 c++-dbus API,但也在这里,我没有找到任何文档!我一直在研究 wpa_supplicant 和 NetworkManager 源代码,但这是一场噩梦!我也一直在研究“低级 D-Bus API”,但这并没有告诉我如何从 D-Bus 消息中提取字符串参数!http://dbus.freedesktop.org/doc/api/html/index.html

Here is some code I wrote to test a little but I really have trouble to extract string values. Sorry for the long source code but if someone want to try it ... My D-Bus configuration seems fine because it "already" catches "StateChanged" signals from wpa_supplicant but cannot print the state:

这是我编写的一些代码来测试一下,但我真的很难提取字符串值。抱歉,源代码很长,但如果有人想尝试一下……我的 D-Bus 配置看起来不错,因为它“已经”从 wpa_supplicant 捕获“StateChanged”信号,但无法打印状态:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>

#include <dbus/dbus.h>

//#include "wpa_supp_dbus.h"
/* Content of wpa_supp_dbus.h */
#define WPAS_DBUS_SERVICE   "fi.epitest.hostap.WPASupplicant"
#define WPAS_DBUS_PATH      "/fi/epitest/hostap/WPASupplicant"
#define WPAS_DBUS_INTERFACE "fi.epitest.hostap.WPASupplicant"

#define WPAS_DBUS_PATH_INTERFACES   WPAS_DBUS_PATH "/Interfaces"
#define WPAS_DBUS_IFACE_INTERFACE   WPAS_DBUS_INTERFACE ".Interface"

#define WPAS_DBUS_NETWORKS_PART "Networks"
#define WPAS_DBUS_IFACE_NETWORK WPAS_DBUS_INTERFACE ".Network"

#define WPAS_DBUS_BSSIDS_PART   "BSSIDs"
#define WPAS_DBUS_IFACE_BSSID   WPAS_DBUS_INTERFACE ".BSSID"

int running = 1;

void stopLoop(int sig)
{
    running = 0;
}

void sendScan()
{
  // TODO !
}

void loop(DBusConnection* conn)
{
    DBusMessage* msg;
    DBusMessageIter args;
    DBusMessageIter subArgs;
    int argType;
    int i;
    int buffSize = 1024;
    char strValue[buffSize];
    const char* member = 0;

    sendScan();

    while (running)
    {
        // non blocking read of the next available message
        dbus_connection_read_write(conn, 0);
        msg = dbus_connection_pop_message(conn);

        // loop again if we haven't read a message
        if (!msg)
        {
            printf("No message received, waiting a little ...\n");
            sleep(1);
            continue;
        }
        else printf("Got a message, will analyze it ...\n");

        // Print the message member
        printf("Got message for interface %s\n",
                dbus_message_get_interface(msg));
        member = dbus_message_get_member(msg);
        if(member) printf("Got message member %s\n", member);

        // Check has argument
        if (!dbus_message_iter_init(msg, &args))
        {
            printf("Message has no argument\n");
            continue;
        }
        else
        {
            // Go through arguments
            while(1)
            {
                argType = dbus_message_iter_get_arg_type(&args);

                if (argType == DBUS_TYPE_STRING)
                {
                    printf("Got string argument, extracting ...\n");

                    /* FIXME : got weird characters
                    dbus_message_iter_get_basic(&args, &strValue);
                    */

                    /* FIXME : segmentation fault !
                    dbus_message_iter_get_fixed_array(
                            &args, &strValue, buffSize);
                    */

                    /* FIXME : segmentation fault !
                    dbus_message_iter_recurse(&args, &subArgs);
                    */

                    /* FIXME : deprecated!
                    if(dbus_message_iter_get_array_len(&args) > buffSize)
                        printf("message content to big for local buffer!");
                    */

                    //printf("String value was %s\n", strValue);
                }
                else
                    printf("Arg type not implemented yet !\n");

                if(dbus_message_iter_has_next(&args))
                    dbus_message_iter_next(&args);
                else break;
            }
            printf("No more arguments!\n");
        }

        // free the message
        dbus_message_unref(msg);
    }
}

int main(int argc, char* argv[])
{
    DBusError err;
    DBusConnection* conn;
    int ret;
    char signalDesc[1024];     // Signal description as string

    // Signal handling
    signal(SIGKILL, stopLoop);
    signal(SIGTERM, stopLoop);

    // Initialize err struct
    dbus_error_init(&err);

    // connect to the bus
    conn = dbus_bus_get(DBUS_BUS_SYSTEM, &err);
    if (dbus_error_is_set(&err))
    {
        fprintf(stderr, "Connection Error (%s)\n", err.message);
        dbus_error_free(&err);
    }
    if (!conn)
    {
        exit(1);
    }

    // request a name on the bus
    ret = dbus_bus_request_name(conn, WPAS_DBUS_SERVICE, 0, &err);
    if (dbus_error_is_set(&err))
    {
        fprintf(stderr, "Name Error (%s)\n", err.message);
        dbus_error_free(&err);
    }

    /* Connect to signal */
    // Interface signal ..
    sprintf(signalDesc, "type='signal',interface='%s'",
            WPAS_DBUS_IFACE_INTERFACE);
    dbus_bus_add_match(conn, signalDesc, &err);
    dbus_connection_flush(conn);
    if (dbus_error_is_set(&err))
    {
        fprintf(stderr, "Match Error (%s)\n", err.message);
        exit(1);
    }

    // Network signal ..
    sprintf(signalDesc, "type='signal',interface='%s'",
            WPAS_DBUS_IFACE_NETWORK);
    dbus_bus_add_match(conn, signalDesc, &err);
    dbus_connection_flush(conn);
    if (dbus_error_is_set(&err))
    {
        fprintf(stderr, "Match Error (%s)\n", err.message);
        exit(1);
    }

    // Bssid signal ..
    sprintf(signalDesc, "type='signal',interface='%s'",
            WPAS_DBUS_IFACE_BSSID);
    dbus_bus_add_match(conn, signalDesc, &err);
    dbus_connection_flush(conn);
    if (dbus_error_is_set(&err))
    {
        fprintf(stderr, "Match Error (%s)\n", err.message);
        exit(1);
    }

    // Do main loop
    loop(conn);

    // Main loop exited
    printf("Main loop stopped, exiting ...\n");

    dbus_connection_close(conn);

    return 0;
}

Any pointer to any nice, complete, low-level C tutorial is strongly appreciated! I'm also planning to do some remote method call, so if the tutorial covers this subject it would be great! Saying I'm not very smart because I don't get it with the official tutorial is also appreciated :-p!

强烈感谢任何指向任何漂亮、完整、低级 C 教程的指针!我还计划进行一些远程方法调用,所以如果教程涵盖了这个主题,那就太好了!说我不是很聪明,因为我没有通过官方教程得到它也很感激:-p!

Or is there another way to communicate with wpa_supplicant (except using wpa_cli)?

或者是否有另一种方式与 wpa_supplicant 通信(使用 wpa_cli 除外)?

EDIT 1:

编辑 1:

Using 'qdbusviewer' and the introspection capabilty, this helped me a lot discovering what and how wpa_supplicant works using dbus. Hopping that this would help someone else!

使用“qdbusviewer”和自省功能,这帮助我发现了 wpa_supplicant 使用 dbus 的工作原理和方式。希望这会帮助别人!

Edit 2:

编辑2:

Will probably come when I'll find a way to read string values on D-Bus!

当我找到一种在 D-Bus 上读取字符串值的方法时,可能会来!

回答by AllThatICode

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!

如果您只需要编写一个 C 程序来与 wpa_supplicant 通信,则不需要使用/了解 dbus 的工作。我对 wpa_cli 的源代码进行了逆向工程。完成了它的实现并使用了 wpa_ctrl.h/c 中提供的功能。这个实现负责一切。你可以使用/修改你想要的任何东西,构建你的可执行文件,你就完成了!

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

这是 wpa_supplicant 的 ctrl_interface 的官方链接:http: //hostap.epitest.fi/wpa_supplicant/devel/ctrl_iface_page.html

回答by hdante

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).

您已经放弃了可以帮助您更轻松地学习 D-Bus 的工具,并且正在使用低级别的 libdbus 实现,所以也许您应该感到痛苦。顺便说一句,您是在谈论 ARM,就像手机 ARM 一样吗?可能有 500 Mhz 和 256 MB RAM?在这种情况下,处理器非常适合使用 glib、Qt 甚至 python。当您编写异步事件驱动代码时,D-Bus 最有用,例如来自 glib 的集成主循环,即使您使用低级 libdbus(它具有连接到 glib 主循环的功能,例如)。

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

由于您使用的是低级库,因此文档就是您已经拥有的:

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

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

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

此外,libdbus 源代码也是文档的一部分:

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

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):

文档的主要入口点是模块页面(特别是公共 API 部分):

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

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

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

对于消息处理,DBusMessage 部分是相关的: 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:

那里有解析项目值的函数的文档。在您的情况下,您从 dbus_message_iter_get_basic 开始。如文档中所述,检索字符串需要一个 const char ** 变量,因为返回的值将指向接收到的消息中预先分配的字符串:

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.
因此,对于 int32,它应该是“dbus_int32_t*”,而对于字符串,它应该是“const char**”。返回值是通过引用,不应被释放。

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.

所以你不能定义一个数组,因为 libdbus 不会将文本复制到你的数组中。如果需要保存字符串,先获取常量字符串引用,然后strcpy到自己的数组。

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.

然后您尝试在不移动迭代器的情况下获得固定数组。您需要在基本字符串和固定数组之间调用下一个迭代器 (dbus_message_iter_next)。在递归到子迭代器之前相同。

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.

最后,您不调用 get_array_len 来获取数组上的元素数。从文档中,它只返回字节数。相反,您使用 iter_next 循环遍历子迭代器,就像您应该使用主迭代器一样。遍历数组末尾后,dbus_message_iter_get_arg_type 将返回 DBUS_TYPE_INVALID。

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

有关更多信息,请阅读参考手册,不要寻找教程。或者只是使用合理的 d-bus 实现:

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

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

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

GIO 的 GDBus 会自动为您的 d-bus 调用创建包装器。

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

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

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

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

etc.

等等。

回答by am3

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);

}

回答by Dassem

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.

如果您不想在项目中包含 GTK/QT 来访问 dbus,那么现在的情况比几年前要好。英特尔的嵌入式 Linux 库中有 dbus API (奇怪的是我记得它是开放的,也许现在只对注册用户开放?)而 systemd sd-bus 库现在提供公共 API。除非您有一个真正受限的嵌入式系统,否则您可能无论如何都运行 systemd。

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

我使用过 GDbus、dbus-cpp 和 sd-bus,虽然我想要一个 C++ 库,但我发现 sd-bus 是最简单且问题最少的体验。我没有尝试它的 C++ 绑定,但它们看起来也不错

#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.

如果上面的例子不能完美运行,我深表歉意。这是我从 C++ 重写为 C 的一个旧项目的摘录(我认为它是 C(-ish),编译器没有抗议并且您要求使用 C)但我无法测试它,因为我所有的加密狗都拒绝与我的现在的桌面。不过,它应该给你一个大致的概念。

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
  1. 确保禁用其他网络管理应用程序(networkmanager、connman...)
  2. 重启 wpa_supplicant 服务
  3. 确保无线接口在 ip link

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

另外,因为现在没有那么详细的记录:您可以通过sd_bus_message_enter_container_exit 对应项访问数组和内部变体值。sd_bus_message_peek_type这样做可能会派上用场。或者sd_bus_message_read_array对于同构数组。