Учебник по D-Bus на C для связи с wpa_supplicant
Я пытаюсь написать код для связи с wpa_supplicant с помощью DBUS. Поскольку я работаю во встроенной системе (ARM), я бы хотел избежать использования Python или GLib. Я задаюсь вопросом, глуп ли я, потому что у меня действительно есть ощущение, что нет никакой хорошей и ясной документации о D-Bus. Даже с официальным я считаю документацию слишком высокой или показанные примеры используют Glib! Документация, на которую я смотрел: http://www.freedesktop.org/wiki/Software/dbus
Я нашел хорошую статью об использовании D-Bus в C: http://www.matthew.ath.cx/articles/dbus
Однако эта статья довольно старая и недостаточно полная! Я также нашел API C++-dbus, но и здесь я не нахожу НИКАКОЙ документации! Я копался в исходном коде wpa_supplicant и NetworkManager, но это настоящий кошмар! Я также искал "низкоуровневый D-Bus API", но это не говорит мне, как извлечь строковый параметр из сообщения D-Bus! http://dbus.freedesktop.org/doc/api/html/index.html
Вот код, который я написал, чтобы немного протестировать, но у меня действительно есть проблемы с извлечением строковых значений. Извините за длинный исходный код, но если кто-то захочет его попробовать... Моя конфигурация D-Bus выглядит нормально, потому что она "уже" перехватывает "StateChanged" сигналы от wpa_supplicant, но не может напечатать состояние:
#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;
}
Любой указатель на любой хороший, полный низкоуровневый учебник по Си настоятельно приветствуется! Я также планирую сделать некоторый удаленный вызов метода, так что если учебник охватывает эту тему, было бы здорово! Сказать, что я не очень умен, потому что я не получаю его с официальным руководством, также приветствуется:-p!
Или есть другой способ связи с wpa_supplicant (кроме использования wpa_cli)?
РЕДАКТИРОВАТЬ 1:
Использование 'qdbusviewer' и возможностей самоанализа помогло мне понять, что и как работает wpa_supplicant с использованием dbus. Прыгая, что это поможет кому-то еще!
Изменить 2:
Вероятно, придет, когда я найду способ чтения строковых значений на D-Bus!
3 ответа
Вы отказались от инструментов, которые помогли бы вам легче освоить D-Bus, и используете низкоуровневую реализацию libdbus, так что, возможно, вы заслуживаете боли. Кстати, вы говорите об ARM, как сотовый телефон ARM? Может быть, 500 МГц и 256 МБ оперативной памяти? В этом случае процессор хорошо подходит для использования 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 не будет копировать текст в ваш массив. Если вам нужно сохранить строку, сначала получите постоянную ссылку на строку, затем strcpy в свой собственный массив.
Затем вы попытались получить фиксированный массив без перемещения итератора. Вам нужен вызов следующего итератора (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
GDBus GIO автоматически создает оболочки для ваших вызовов по шине D-Bus.
http://qt-project.org/doc/qt-4.8/intro-to-dbus.html
http://dbus.freedesktop.org/doc/dbus-python/doc/tutorial.html
и т.п.
Я сомневаюсь, что этот ответ будет по-прежнему актуален для автора этого вопроса, но для любого, кто сталкивается с этим, как я:
Сейчас ситуация лучше, чем все эти годы назад, если вы не хотите включать GTK/QT в свой проект для доступа к dbus. В Embedded Linux Library от Intel есть API-интерфейс dbus (странно, я помню, что он открыт, может быть, он предназначен только для зарегистрированных пользователей?), А библиотека 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), но я не могу проверить его, так как все мои ключи отказываются работать с моим рабочий стол прямо сейчас. Это должно дать вам общее представление, хотя.
Обратите внимание, что вы, вероятно, столкнетесь с несколькими магическими или полумагическими проблемами. Для обеспечения бесперебойной разработки / тестирования сделайте следующее:
- убедитесь, что другие приложения управления сетью отключены (networkmanager, connman...)
- перезапустите сервис wpa_supplicant
- убедитесь, что беспроводной интерфейс включен
ip link
Кроме того, потому что это не очень хорошо документировано прямо сейчас: Вы можете получить доступ к массивам и внутренним вариантным значениям с помощью sd_bus_message_enter_container
и _выходить коллегу. sd_bus_message_peek_type
может пригодиться при этом. Или же sd_bus_message_read_array
для однородного массива.
Вам не нужно использовать / понимать работу dbus. Если вам просто нужно написать программу на C для связи с wpa_supplicant. Я реверс-инжиниринг исходного кода wpa_cli. Прошел его реализацию и использовал функции, представленные в wpa_ctrl.h / c. Эта реализация заботится обо всем. Вы можете использовать / изменять все, что вы хотите, создать свой исполняемый файл, и все готово!
Вот официальная ссылка на ctrl_interface wpa_supplicant: http://hostap.epitest.fi/wpa_supplicant/devel/ctrl_iface_page.html
Ниже приведенный фрагмент работает для меня
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);
}