mvidner / ruby-dbus

A Ruby binding for DBus
GNU Lesser General Public License v2.1
170 stars 48 forks source link

NetworkManager.Settings.Connection cannot round-trip, calling Update with what GetSettings returned #124

Open mvidner opened 2 years ago

mvidner commented 2 years ago

(reported by @imobachgs)

We would like to use ruby-dbus to interact with NetworkManager through D-Bus. The following example tries to use the method to Update a connection. It basically asks for the current configuration (GetSettings) an tries to apply the same configuration (calling Update).

require "dbus"

bus = ::DBus::SystemBus.instance
service = bus.service("org.freedesktop.NetworkManager")
settings_obj = service.object("/org/freedesktop/NetworkManager/Settings")

# find a settings object (a.k.a. connections)
settings_path, *_others = settings_obj["org.freedesktop.NetworkManager.Settings"].ListConnections.first

# set settings configuration
nm_settings = service.object(settings_path)
conn_settings = nm_settings["org.freedesktop.NetworkManager.Settings.Connection"].GetSettings.first

puts "Current configuration", conn_settings.inspect
nm_settings.Update(conn_settings)

However, it fails because data types for empty values (empty arrays and hashes) are not what NetworkManager expects. Our guess is that there is no typing information available, so ruby-dbus cannot figure out what to send in such cases.

/usr/lib64/ruby/gems/3.1.0/gems/ruby-dbus-0.18.1/lib/dbus/bus.rb:364:in `block in send_sync_or_async': connection.permissions: wrong type; should be a list of strings.; caused by 3 sender=:1.7 -> dest=:1.499 serial=1943 reply_serial=12 path=; interface=; member= error_name=org.freedesktop.NetworkManager.Settings.Connection.InvalidProperty (DBus::Error)
        from /usr/lib64/ruby/gems/3.1.0/gems/ruby-dbus-0.18.1/lib/dbus/bus.rb:552:in `process'
        from /usr/lib64/ruby/gems/3.1.0/gems/ruby-dbus-0.18.1/lib/dbus/bus.rb:486:in `send_sync'
        from /usr/lib64/ruby/gems/3.1.0/gems/ruby-dbus-0.18.1/lib/dbus/bus.rb:363:in `send_sync_or_async'
        from /usr/lib64/ruby/gems/3.1.0/gems/ruby-dbus-0.18.1/lib/dbus/proxy_object_interface.rb:70:in `block (2 levels) in define_method_from_descriptor'
        from /usr/lib64/ruby/gems/3.1.0/gems/ruby-dbus-0.18.1/lib/dbus/proxy_object.rb:113:in `call'
        from /usr/lib64/ruby/gems/3.1.0/gems/ruby-dbus-0.18.1/lib/dbus/proxy_object.rb:113:in `block (3 levels) in define_shortcut_methods'
        from a.rb:16:in `<main>'
/usr/lib64/ruby/gems/3.1.0/gems/ruby-dbus-0.18.1/lib/dbus/bus.rb:364:in `block in send_sync_or_async': connection.permissions: wrong type; should be a list of strings. (DBus::Error)
        from /usr/lib64/ruby/gems/3.1.0/gems/ruby-dbus-0.18.1/lib/dbus/bus.rb:552:in `process'
        from /usr/lib64/ruby/gems/3.1.0/gems/ruby-dbus-0.18.1/lib/dbus/bus.rb:486:in `send_sync'
        from /usr/lib64/ruby/gems/3.1.0/gems/ruby-dbus-0.18.1/lib/dbus/bus.rb:363:in `send_sync_or_async'
        from /usr/lib64/ruby/gems/3.1.0/gems/ruby-dbus-0.18.1/lib/dbus/proxy_object_interface.rb:70:in `block (2 levels) in define_method_from_descriptor'
        from /usr/lib64/ruby/gems/3.1.0/gems/ruby-dbus-0.18.1/lib/dbus/proxy_object.rb:113:in `call'
        from /usr/lib64/ruby/gems/3.1.0/gems/ruby-dbus-0.18.1/lib/dbus/proxy_object.rb:113:in `block (3 levels) in define_shortcut_methods'
        from change-nm.rb:16:in `<main>'

Even if we do not send any empty attribute (cleaning up the settings), we find an additional problem with ipv4.address-data:

/usr/lib64/ruby/gems/3.1.0/gems/ruby-dbus-0.18.1/lib/dbus/bus.rb:364:in `block in send_sync_or_async': ipv4.address-data: can't set property of type 'aa{sv}' from value of type 'av'; caused by 3 sender=:1.7 -> dest=:1.545 serial=2082 reply_serial=12 path=; interface=; member= error_name=org.freedesktop.NetworkManager.Settings.Connection.InvalidProperty (DBus::Error)

Are our assumptions right? Do we have a way to workaround this problem?

Thanks!

mvidner commented 2 years ago

I've had success on my machine with applying this transformation between GetSettings and Update

def fix_types(settings)
  types = {
    "connection.permissions" => "as",
    "802-11-wireless.mac-address" => "ay",
    "802-11-wireless.mac-address-blacklist" => "as",
    "802-11-wireless.ssid" => "ay",
    "ipv4.address-data" => "aa{sv}",
    "ipv4.addresses" => "aau",
    "ipv4.dns" => "au",
    "ipv4.dns-search" => "as",
    "ipv4.route-data" => "aa{sv}",
    "ipv4.routes" => "aau",
    "ipv6.address-data" => "aa{sv}",
    "ipv6.addresses" => "a(ayuay)",
    "ipv6.dns" => "aay",
    "ipv6.dns-search" => "as",
    "ipv6.route-data" => "aa{sv}",
    "ipv6.routes" => "a(ayuayu)"
  }

  types.each do |dot_prop, type|
    section, prop = dot_prop.split "."
    settings[section][prop] = ::DBus::Data.make_typed(type, settings[section][prop])
  end
end

fix_types(conn_settings)

See https://www.rubydoc.info/gems/ruby-dbus/0.18.1/DBus/Data#make_typed-class_method

"A-Ayu-Ayu!" is my favorite type from now on!

mvidner commented 2 years ago

In general, I'd like to improve the library so that you'll be able to specify that a method call (like GetSettings) should only return DBus::Data values instead of plain Ruby values. They need unwrapping but are the perfect fit for round-tripping like this.

imobachgs commented 2 years ago

It works just fine. Having an API to do this kind of type annotation is more than enough for us. Thanks!