Search code examples
c++glibdbusconnman

Agent interface methods won't be called when connecting to the protected network in DBus


I am developing an application to connect to secure WiFi networks using the Connman DBus API. I have read that I need to register an object that implements the net.connman.Agent interface so that DBus can call my methods. So far I've been able to do something very close to what I need, however when I run the program, if I've never connected to that network before, the "Connect" function times out. If I put a breakpoint on the Agent event handler, it will never get called. I have this code for agent registration:

WiFiAgent::WiFiAgent(GDBusConnection *inputConnection, Poco::JSON::Object::Ptr credentials)
: connection(inputConnection), parameters(std::move(credentials)) {
    static const GDBusInterfaceVTable vtable = {
            .method_call  = handleMethodCall,
            .get_property = nullptr,
            .set_property = nullptr,
    };
    static GError* error = nullptr;

    objectId = g_dbus_connection_register_object(
            connection,
            WiFiAgent::ourAgentPath,
            WiFiAgent::introspectionWrapper->interfaces[0],
            &vtable,
            parameters.get(),
            nullptr,
            &error
    );
    if(objectId == 0 || error != nullptr)
        throw GlibException("Register WiFi agent", error);

    GVariant* agentPathVariant = g_variant_new("(o)", WiFiAgent::ourAgentPath);
    if(agentPathVariant == nullptr)
        throw std::runtime_error("Register WiFi agent: g_variant_new failed.");

    GVariant* result = g_dbus_connection_call_sync(connection, "net.connman", "/", "net.connman.Manager", 
"RegisterAgent", agentPathVariant, nullptr, G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error);
    if(result  == nullptr || error != nullptr)
        throw GlibException("Register WiFi agent", error);
}

this code for event handler:

void WiFiAgent::handleMethodCall(GDBusConnection *, const gchar *, 
    const gchar *, const gchar *, const gchar *method, 
    GVariant *methodParameters, GDBusMethodInvocation *invocation, gpointer userdata) {
    std::cout << "Method got called." << std::endl;
}

and this code for the introspection:

static inline class IntrospectionWrapper final {
    GDBusNodeInfo* introspection = nullptr;
    static constexpr auto introspectionXML =
        "<node>"
        "  <interface name='net.connman.Agent'>"
        "    <method name='RequestInput'>"
        "      <arg type='o' name='service' direction='in'/>"
        "      <arg type='a{sv}' name='fields' direction='in'/>"
        "      <arg type='a{sv}' name='fields' direction='out'/>"
        "    </method>"
        "    <method name='ReportError'>"
        "      <arg type='o' name='service' direction='in'/>"
        "      <arg type='s' name='error' direction='in'/>"
        "    </method>"
        "  </interface>"
        "</node>";

public:
    IntrospectionWrapper() {
        GError* error = nullptr;
        introspection = g_dbus_node_info_new_for_xml(introspectionXML, &error);
        if(introspection == nullptr || error != nullptr)
           std::cerr << GlibException("Agent introspection construction", error) << std::endl;
    }
    GDBusNodeInfo* operator->() { return introspection; };
    GDBusNodeInfo* get() { return introspection; }
} introspectionWrapper;

Everything works fine and no errors occur, but the "Connect" function called on the specified service interface fails with a timeout error:

Glib error with code 24 - g-io-error-quark - Timeout was reached

I call this function this way:

void DBusManipulator::connectToTheNetwork(GDBusProxy *network) {
    GError* error = nullptr;
    g_dbus_proxy_call_sync(network, "Connect", nullptr, G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error);
    if(error != nullptr)
        throw GlibException("Connect to the network", error);

    const auto state = variantGetValue(getNetworkProperty(network, "State"));
    if(state != "online" && state != "ready")
        throw std::runtime_error("Connect to the WiFi network: connection failed");

    std::cout << "Connected to the network successfully." << std::endl;
}

Where the network get's received this way:

GDBusProxy* network = g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SYSTEM,G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, nullptr, "net.connman", servicePath, "net.connman.Service", nullptr, &error);
if(network == nullptr || error != nullptr)
    throw GlibException("Get network by name", error);

Solution

  • Managed to solve the problem by changing Connect method call to asynchronous and running main loop:

    g_dbus_proxy_call(G_DBUS_PROXY(network), "Connect", g_variant_new("()"), 
        G_DBUS_CALL_FLAGS_NONE, -1, nullptr, finisher, data);
    g_main_loop_run(mainloop);
    
    if(data ... connectionFlag == false) /* Checking finisher result. */
        throw std::runtime_error("Connection failed");
    

    Finisher function:

    static constexpr auto finisher = [](GObject*, GAsyncResult* result, gpointer userdata) {
        auto* parameters = static_cast<...*>(userdata);
        GDBusProxy* network = parameters->...;
    
        GError* error = nullptr;
        GVariant* tResult = g_dbus_proxy_call_finish(G_DBUS_PROXY(network), result, &error);
        if (tResult != nullptr && error == nullptr)
            ...; /* Success, setting up the result flag. */
    
        g_main_loop_quit(mainloop); 
        /* Quits the main loop and unlocks the main thread */
    };
    

    Now agent methods are called right after the Connect method.