用C语言编写的D-Bus教程,用于与wpa_supplicant通信

22
我正在尝试编写一些代码来使用DBUS与wpa_supplicant通信。由于我正在嵌入式系统(ARM)中工作,所以我想避免使用Python或GLib。我在想我是不是很蠢,因为我真的觉得没有关于D-Bus的好的、清晰的文档。即使是官方的文档,我要么发现文档太高级了,要么展示的例子使用了GLib!我查看过的文档有:http://www.freedesktop.org/wiki/Software/dbus
我找到了一篇关于在C语言中使用D-Bus的好文章:http://www.matthew.ath.cx/articles/dbus
然而,这篇文章相当老旧,而且不够完整!我也找到了c++-dbus API,但同样地,我找不到任何文档!我一直在研究wpa_supplicant和NetworkManager的源代码,但那真是个噩梦!我也研究了“低级D-Bus API”,但这并没有告诉我如何从D-Bus消息中提取字符串参数!http://dbus.freedesktop.org/doc/api/html/index.html
这里是我写的一些测试代码,但我真的很难提取字符串值。对于这段冗长的源代码,如果有人想尝试一下...我的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;
}

非常感谢您能提供一些关于完整、低级别C语言教程的指引。我还计划进行一些远程方法调用,如果该教程涵盖了这个主题,那就太好了!如果您说我因为在官方教程中没有理解而不够聪明,那也是可以接受的 :-p!
除了使用wpa_cli之外,还有其他与wpa_supplicant通信的方法吗? 编辑1: 使用“qdbusviewer”和内省功能,这帮助我很多地发现了wpa_supplicant如何使用dbus工作。希望这能帮助其他人! 编辑2: 当我找到一种在D-Bus上读取字符串值的方法时,可能会再次更新。

1
你找到了在D-Bus上读取字符串值的方法吗? - Guilherme Torres Castro
4个回答

6
你已放弃了能够帮助你更轻松学习D-Bus的工具,而是使用低级别的libdbus实现,所以也许你应该承受这种痛苦。顺便问一下,你是在说ARM,像是一个手机ARM?拥有大约500 MHz和256 MB RAM?在这种情况下,处理器非常适合使用glib、Qt甚至python。当你编写异步事件驱动代码时,D-Bus最有用,配合集成的主循环,例如从glib开始,即使你使用低级别的libdbus(它有函数连接到glib主循环,例如)。

由于你正在使用低级别的库,那么文档就是你已经拥有的:

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

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

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

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

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

对于消息处理,DBusMessage部分是相关的: DBusMessage

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

因此,对于int32,它应该是“dbus_int32_t *”,对于字符串,则为“const char **”。返回的值是通过引用返回的,不应释放。

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

然后您尝试获取固定数组而没有移动迭代器。在基本字符串和固定数组之间,需要调用下一个迭代器(dbus_message_iter_next)。同样,在递归进入子迭代器之前也需要调用。

最后,你不需要调用get_array_len来获取数组中元素的数量。从文档中可以看出,它只返回字节数。相反,你需要像处理主迭代器一样使用iter_next循环遍历子迭代器。在你迭代到数组末尾之后,dbus_message_iter_get_arg_type将返回DBUS_TYPE_INVALID。
如果需要更多信息,请阅读参考手册,而不是寻找教程。或者直接使用合理的d-bus实现。

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

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

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

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

等等。


2
如果你只是需要编写一个与wpa_supplicant通信的C程序,那么你并不需要使用/理解dbus的工作原理。我反向工程了wpa_cli的源代码,阅读了其实现并使用了wpa_ctrl.h/c中提供的函数。这种实现可以处理一切事情。你可以使用/修改任何你想要的内容,构建可执行文件,然后就完成了! 这里是wpa_supplicant ctrl_interface的官方链接:http://hostap.epitest.fi/wpa_supplicant/devel/ctrl_iface_page.html

没错,我也使用了相同的方法。wpa_cli.c 和 wpa_ctrl.h 中包含所有内容,可供复制/粘贴使用。根据上面的链接,控制接口是与 wpa_supplicant 通信的推荐方式。不需要费力地使用 d-bus。 - FractalSpace

2
我怀疑这个回答对于这个问题的作者来说可能已经不再相关了,但是对于像我一样偶然发现这个问题的人来说:

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

我曾经使用过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;
}

如果上面的例子不完美地运作,我向您道歉。这是我从C++重写到C的一个旧项目摘录(我认为它是C(-ish),编译器没有抗议,而您要求使用C),但我现在无法测试它,因为我的所有加密狗都拒绝与我的桌面电脑一起工作。不过,它应该能给你一个大致的想法。

请注意,您可能会遇到几个神奇或半神奇的问题。 为确保平稳开发/测试,请执行以下操作:

  1. 确保禁用其他网络管理应用程序(networkmanager、connman等)
  2. 重新启动wpa_supplicant服务
  3. 确保无线接口在ip link中处于UP状态

此外,由于目前文档不是特别详细: 您可以通过sd_bus_message_enter_container和_exit对应项访问数组和内部变量值。在这样做时,sd_bus_message_peek_type可能会很方便。或者使用sd_bus_message_read_array来读取同类数组。


0
以下代码片段对我有效。
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);

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接