c++linuxwifidbus

'RequestInput' Callback in sdbus-c++ Triggered After Connection Timeout


I'm writing code to connect a particular Wi-Fi device using Connection Manager and sdbus-c++. When calling the Connect function, the system asks for a password via RequestInput. The password is provided to the system by replying to RequestInput using the callback function installed by addMatch. However, while running the code, the callback function is called after the connection times out. Ideally, the callback function should be called immediately when the Connect function is called.

When I monitor this method call from the terminal using the command:

dbus-monitor --system "interface='net.connman.Agent',member='RequestInput'"

I receive it at the right time.

Here is my code:

#include <iostream>
#include <sdbus-c++/sdbus-c++.h>
#include <map>
#include <vector>
#include <memory>
#include <functional>
#include <string>

void handle_message(sdbus::Message message) {
    std::cout << "Received RequestInput method call" << std::endl;
}

int monitor_pass_req(std::unique_ptr<sdbus::IConnection>& connection) {
    try {
        // Define handler
        sdbus::message_handler handler = [](sdbus::Message message) {
            handle_message(std::move(message));
        };
        
        auto match_rule = "type='method_call',interface='net.connman.Agent',member='RequestInput'";
        connection->addMatch(match_rule, handler);
    } catch (sdbus::Error& e) {
        std::cerr << "D-Bus error:" << e.what() << std::endl;
        return -1;
    }
    return 0;
}

int set_wifi_power(std::unique_ptr<sdbus::IConnection>& connection, bool power_state) {
    try {
        sdbus::ServiceName service("net.connman");
        sdbus::ObjectPath objectpath("/net/connman/technology/wifi");
        std::unique_ptr<sdbus::IProxy> proxy = sdbus::createProxy(*connection, std::move(service), std::move(objectpath));
        proxy->callMethod("SetProperty").onInterface("net.connman.Technology").withArguments("Powered", sdbus::Variant(power_state));
    } catch (sdbus::Error& e) {
        std::cerr << "D-Bus error:" << e.what() << std::endl;
        return -1;
    }
    return 0;
}

int register_agent(std::unique_ptr<sdbus::IConnection>& connection, sdbus::ObjectPath& path) {
    try {
        sdbus::ServiceName service("net.connman");
        sdbus::ObjectPath objectpath("/");
        std::unique_ptr<sdbus::IProxy> proxy = sdbus::createProxy(*connection, std::move(service), std::move(objectpath));
        proxy->callMethod("RegisterAgent").onInterface("net.connman.Manager").withArguments(path);
    } catch (sdbus::Error& e) {
        std::cerr << "D-Bus error:" << e.what() << std::endl;
        return -1;
    }
    return 0;
}

int connect_wifi_dev(std::unique_ptr<sdbus::IConnection>& connection, sdbus::ObjectPath& path) {
    try {
        sdbus::ServiceName service("net.connman");
        sdbus::ObjectPath objectpath(path);
        std::unique_ptr<sdbus::IProxy> proxy = sdbus::createProxy(*connection, std::move(service), std::move(objectpath));
        proxy->callMethod("Connect").onInterface("net.connman.Service");
    } catch (sdbus::Error& e) {
        std::cerr << "D-Bus error:" << e.what() << std::endl;
        return -1;
    }
    return 0;
}

int scan_wifi(std::unique_ptr<sdbus::IConnection>& connection) {
    try {
        sdbus::ServiceName service("net.connman");
        sdbus::ObjectPath objectpath("/net/connman/technology/wifi");
        std::unique_ptr<sdbus::IProxy> proxy = sdbus::createProxy(*connection, std::move(service), std::move(objectpath));
        proxy->callMethod("Scan").onInterface("net.connman.Technology");
    } catch (const sdbus::Error& e) {
        std::cerr << "D-Bus error:" << e.what() << std::endl;
        return -1;
    }
    return 0;
}

int main() {
    std::unique_ptr<sdbus::IConnection> connection = sdbus::createSystemBusConnection();
    bool power_state = true;

    int ret = set_wifi_power(connection, power_state);
    if (0 == ret) std::cout << "Wi-Fi powered successfully\n";

    sdbus::ObjectPath mypath("/test/wifi");
    ret = register_agent(connection, mypath);
    if (0 == ret) std::cout << "Agent Registered\n";

    ret = monitor_pass_req(connection);
    if (0 == ret) std::cout << "Add match added\n";

    connection->enterEventLoopAsync();

    ret = scan_wifi(connection);
    if (0 == ret) std::cout << "Scan completed\n";

    sdbus::ObjectPath devpath("/net/connman/service/wifi_340a33303465_6d616873686f6f6b_managed_psk");
    ret = connect_wifi_dev(connection, devpath);
    if (0 == ret) std::cout << "Connection completed\n";

    while (true) {}

    return 0;
}

Expected Behavior: The RequestInput callback function should be called immediately when the Connect function is invoked, prompting the user for the Wi-Fi password.

Actual Behavior: The RequestInput callback function is called after the connection times out.

Output:

Wi-Fi powered successfully
Agent Registered
Add match added
Scan completed
D-Bus error:[org.freedesktop.DBus.Error.Timeout] Connection timed out
Received RequestInput method call

Any insights or suggestions on how to ensure the callback function is triggered at the correct time would be greatly appreciated. Thank you


Solution

  • Match rules aren't quite what you want in this case. Match rules are useful when introspecting DBus messages that would have been sent and processed anyway (for example when listening to bus messages, as e.g. dbus-monitor does), but not to actually react to method calls.

    In your use case, you want to tell connman that your program is able to answer password input requests. And to do so you must provide a DBus object that has the corresponding methods that connman may call. Just adding a match for messages doesn't magically create the DBus object, you still have to tell the DBus system daemon that this object actually exists (by in fact creating it).

    So instead of

        sdbus::ObjectPath mypath("/test/wifi");
        ret = register_agent(connection, mypath);
        if (0 == ret) std::cout << "Agent Registered\n";
    
        ret = monitor_pass_req(connection);
        if (0 == ret) std::cout << "Add match added\n";
    

    what you actually want to do would be something along the lines of (untested, just a sketch):

        sdbus::ObjectPath mypath("/test/wifi");
        auto responder = sdbus::createObject(*connection, mypath);
        sdbus::InterfaceName agentInterface{"net.connman.Agent"};
        responder->addVTable(sdbus::MethodVTableItem{sdbus::MethodName{"Release"}, sdbus::Signature{""}, {}, sdbus::Signature{""}, {}, [&] (sdbus::MethodCall message) { /* ... */ }, {}},
                             sdbus::MethodVTableItem{sdbus::MethodName{"ReportError"}, sdbus::Signature{"os"}, {"service", "error"}, sdbus::Signature{""}, {}, [&] (sdbus::MethodCall message) { /* ... */ }, {}},
                             sdbus::MethodVTableItem{sdbus::MethodName{"RequestBrowser"}, sdbus::Signature{"os"}, {"service", "url"}, sdbus::Signature{""}, {}, [&] (sdbus::MethodCall message) { /* ... */ }, {}},
                             sdbus::MethodVTableItem{sdbus::MethodName{"RequestInput"}, sdbus::Signature{"oa{sv}"}, {"service", "fields"}, sdbus::Signature{"a{sv}"}, {"result"}, [&] (sdbus::MethodCall message) { /* ... */ }, {}})
            .forInterface(agentInterface);
    
        ret = register_agent(connection, mypath);
        if (0 == ret) std::cout << "Agent Registered\n";
    

    This will first create a DBus object on your side, and register the net.connman.Agent interface with it, including the methods that are required by that interface (in the above example just dummy stubs).

    Then it will call your register_agent() method to register the newly created DBus object with connman.

    You don't need a match here at all, since method calls to your own objects are always dispatched properly, even without a match.

    You'd then need to actually implement the methods (replace the /* ... */ in the lambdas in the vtable with actually useful code), most notably the RequestInput method, in order for connman to properly work with your code.