diff --git a/doc/dbus/bus/org.opensuse.Agama.Network1.Connection.IPv4.bus.xml b/doc/dbus/bus/org.opensuse.Agama.Network1.Connection.IPv4.bus.xml new file mode 120000 index 0000000000..7b1c091d22 --- /dev/null +++ b/doc/dbus/bus/org.opensuse.Agama.Network1.Connection.IPv4.bus.xml @@ -0,0 +1 @@ +org.opensuse.Agama.Network1.Connection.bus.xml \ No newline at end of file diff --git a/doc/dbus/bus/org.opensuse.Agama.Network1.Connection.Wireless.bus.xml b/doc/dbus/bus/org.opensuse.Agama.Network1.Connection.Wireless.bus.xml new file mode 120000 index 0000000000..7b1c091d22 --- /dev/null +++ b/doc/dbus/bus/org.opensuse.Agama.Network1.Connection.Wireless.bus.xml @@ -0,0 +1 @@ +org.opensuse.Agama.Network1.Connection.bus.xml \ No newline at end of file diff --git a/doc/dbus/bus/org.opensuse.Agama.Network1.Connection.bus.xml b/doc/dbus/bus/org.opensuse.Agama.Network1.Connection.bus.xml new file mode 100644 index 0000000000..91545a74fe --- /dev/null +++ b/doc/dbus/bus/org.opensuse.Agama.Network1.Connection.bus.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/dbus/bus/org.opensuse.Agama.Network1.Connections.bus.xml b/doc/dbus/bus/org.opensuse.Agama.Network1.Connections.bus.xml new file mode 100644 index 0000000000..1987d9850c --- /dev/null +++ b/doc/dbus/bus/org.opensuse.Agama.Network1.Connections.bus.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/dbus/bus/org.opensuse.Agama.Network1.Device.bus.xml b/doc/dbus/bus/org.opensuse.Agama.Network1.Device.bus.xml new file mode 100644 index 0000000000..2d7d5f9708 --- /dev/null +++ b/doc/dbus/bus/org.opensuse.Agama.Network1.Device.bus.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/dbus/bus/org.opensuse.Agama.Network1.Devices.bus.xml b/doc/dbus/bus/org.opensuse.Agama.Network1.Devices.bus.xml new file mode 100644 index 0000000000..eff3ad0179 --- /dev/null +++ b/doc/dbus/bus/org.opensuse.Agama.Network1.Devices.bus.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/dbus/org.opensuse.Agama.Network1.Connection.IPv4.doc.xml b/doc/dbus/org.opensuse.Agama.Network1.Connection.IPv4.doc.xml new file mode 100644 index 0000000000..c7d36731e2 --- /dev/null +++ b/doc/dbus/org.opensuse.Agama.Network1.Connection.IPv4.doc.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/doc/dbus/org.opensuse.Agama.Network1.Connection.Wireless.doc.xml b/doc/dbus/org.opensuse.Agama.Network1.Connection.Wireless.doc.xml new file mode 100644 index 0000000000..bad0fb7f53 --- /dev/null +++ b/doc/dbus/org.opensuse.Agama.Network1.Connection.Wireless.doc.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/doc/dbus/org.opensuse.Agama.Network1.Connection.doc.xml b/doc/dbus/org.opensuse.Agama.Network1.Connection.doc.xml new file mode 100644 index 0000000000..20a7bf0f71 --- /dev/null +++ b/doc/dbus/org.opensuse.Agama.Network1.Connection.doc.xml @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/doc/dbus/org.opensuse.Agama.Network1.Connections.doc.xml b/doc/dbus/org.opensuse.Agama.Network1.Connections.doc.xml new file mode 100644 index 0000000000..3ea5d4387f --- /dev/null +++ b/doc/dbus/org.opensuse.Agama.Network1.Connections.doc.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/dbus/org.opensuse.Agama.Network1.Device.doc.xml b/doc/dbus/org.opensuse.Agama.Network1.Device.doc.xml new file mode 100644 index 0000000000..93fd13a157 --- /dev/null +++ b/doc/dbus/org.opensuse.Agama.Network1.Device.doc.xml @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/doc/dbus/org.opensuse.Agama.Network1.Devices.doc.xml b/doc/dbus/org.opensuse.Agama.Network1.Devices.doc.xml new file mode 100644 index 0000000000..d11df5f3cc --- /dev/null +++ b/doc/dbus/org.opensuse.Agama.Network1.Devices.doc.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + diff --git a/doc/network_support_design.md b/doc/network_support_design.md index 09d3cb8a76..aac39ecfab 100644 --- a/doc/network_support_design.md +++ b/doc/network_support_design.md @@ -52,18 +52,19 @@ Based on the situation described above, we considered these approaches: 2. Keep the status quo, extend the web UI and rely on `nmcli` for the CLI-based installation. For automation, we could rely on third-party tool. -Although it might be harder, option 1 looks more consistent: you just need Agama D-Bus interface to -perform an installation. +Although it might be harder, option 1 looks more consistent: you can install your system just using +Agama D-Bus interface. ## Adding our own D-Bus interface Adding a D-Bus interface does not mean that we need to implement a full solution to set up the -network. Actually, we can leverage some third-party tool to do the hard work. The idea is to build a -good enough interface to support our use cases. +network. The idea is to build a good enough API to support our use cases. -### Why not YaST2? +Initially, we though about adding the D-Bus API on top of [nmstate](https://nmstate.io/), although +it covers a different use-case. After playing a bit with this idea, we decided to come up with a +solution more aligned with our needs. -You might be wondering, why not use YaST2 itself? Let's see some reasons: +As an alternative, you might be wondering why not use YaST2 itself? Let's see some reasons: * It does not implement support for reading the NetworkManager configuration. * It is not able to talk to NetworkManager D-Bus interface. It configures NetworkManager by writing @@ -71,32 +72,21 @@ You might be wondering, why not use YaST2 itself? Let's see some reasons: * It is Ruby-based, so we might consider a Rust-based solution. It is not a language problem, but we would like to reduce the memory consumption. -### The proposal +## The proposal -Our proposal is to build a D-Bus service that wraps around a third-party tool that takes care of the -hard part. We considered [Netplan](https://netplan.io/) and [nmstate](https://nmstate.io/), although -we decided to use the latter (see [Third-party tools](#third-party-tools)). +Agama's network service is responsible for holding the network configuration for the installer. It +should be agnostic from the used network service, although in the short-term it will support +NetworkManager only. Therefore, the current solution is influenced by NetworkManager itself. -Unfortunately, those tools are missing support for some cases (e.g., wireless configuration or udev -handling), but we can add such a support in our wrapper. +In a first version, the API is composed of the following objects: -## Third-party tools +* Network devices. Each one is available as an object under `/org/opensuse/Agama/Network1/devices/*` + exposing the current status[^1]. +* Connections (or configurations). They are exposed as `/org/opensuse/Agama/Network1/connections/*`. + Depending on the type of connection, those objects implements different interfaces like + `org.opensuse.Agama.Network1.IPv4`, `org.opensuse.Agama.Network1.Wireless`, etc. -Both [Netplan](https://netplan.io/) and [nmstate](https://nmstate.io/) are tools that are able to -set up NetworkManager to meet a given configuration. Although we decided on `nmstate`, let's -have a look to some of their characteristics: +This API could be expanded in the future to provide a list of access points, emit signals when the +configuration changes, provide more information about the network devices, etc. -### nmstate - -- Based on devices/interfaces (the concept of connection does not exist). -- Designed to support multiple network providers, but at this point only NetworkManager is - supported. -- Offered as a Rust library. -- Bindings for several languages and a CLI. -- Support for WiFi and VPNs is missing. - -### Netplan - -- Written in Python. That's the main reason to discard this option. -- Designed to support multiple network providers, nowadays it supports NetworkManager and networkd. -- It offers a rather limited D-Bus API. +[^1]: By now it only exposes some basic data, as the current status is only needed by the web UI. diff --git a/rust/Cargo.lock b/rust/Cargo.lock index eecc69875a..e2b0aab33e 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -27,7 +27,9 @@ dependencies = [ name = "agama-dbus-server" version = "0.1.0" dependencies = [ + "agama-lib", "agama-locale-data", + "agama-network", "anyhow", "async-std", "zbus", @@ -40,7 +42,7 @@ version = "1.0.0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -73,6 +75,18 @@ dependencies = [ "serde", ] +[[package]] +name = "agama-network" +version = "0.1.0" +dependencies = [ + "agama-lib", + "async-std", + "parking_lot", + "thiserror", + "uuid", + "zbus", +] + [[package]] name = "ahash" version = "0.8.3" @@ -88,18 +102,67 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.7.20" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" dependencies = [ "memchr", ] +[[package]] +name = "anstream" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is-terminal", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" + +[[package]] +name = "anstyle-parse" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "anstyle-wincon" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" +dependencies = [ + "anstyle", + "windows-sys 0.48.0", +] + [[package]] name = "anyhow" -version = "1.0.69" +version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" +checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" [[package]] name = "async-attributes" @@ -108,7 +171,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" dependencies = [ "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -134,9 +197,9 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17adb73da160dfb475c183343c8cccd80721ea5a605d3eb57125f0a7b7a92d0b" +checksum = "6fa3dc5f2a8564f07759c008b9109dc0d39de92a88d5588b8a5036d286383afb" dependencies = [ "async-lock", "async-task", @@ -175,22 +238,22 @@ dependencies = [ [[package]] name = "async-io" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c374dda1ed3e7d8f0d9ba58715f924862c63eae6849c92d3a18e7fbde9e2794" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" dependencies = [ "async-lock", "autocfg", + "cfg-if", "concurrent-queue", "futures-lite", - "libc", "log", "parking", "polling", + "rustix", "slab", "socket2", "waker-fn", - "windows-sys 0.42.0", ] [[package]] @@ -202,15 +265,33 @@ dependencies = [ "event-listener", ] +[[package]] +name = "async-process" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9d28b1d97e08915212e2e45310d47854eafa69600756fc735fb788f75199c9" +dependencies = [ + "async-io", + "async-lock", + "autocfg", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix", + "signal-hook", + "windows-sys 0.48.0", +] + [[package]] name = "async-recursion" -version = "1.0.2" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b015a331cc64ebd1774ba119538573603427eaace0a1950c423ab971f903796" +checksum = "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.16", ] [[package]] @@ -242,26 +323,26 @@ dependencies = [ [[package]] name = "async-task" -version = "4.3.0" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524" +checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae" [[package]] name = "async-trait" -version = "0.1.66" +version = "0.1.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b84f9ebcc6c1f5b8cb160f6990096a5c127f423fcb6e1ccc46c370cbdfb75dfc" +checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.16", ] [[package]] name = "atomic-waker" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "debc29dde2e69f9e47506b525f639ed42300fc014a3e007832592448fa8e4599" +checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" [[package]] name = "autocfg" @@ -307,9 +388,9 @@ dependencies = [ [[package]] name = "blocking" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c67b173a56acffd6d2326fb7ab938ba0b00a71480e14902b2591c87bc5741e8" +checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65" dependencies = [ "async-channel", "async-lock", @@ -317,13 +398,14 @@ dependencies = [ "atomic-waker", "fastrand", "futures-lite", + "log", ] [[package]] name = "bumpalo" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" [[package]] name = "bytecount" @@ -383,61 +465,72 @@ dependencies = [ [[package]] name = "clap" -version = "4.1.8" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d7ae14b20b94cb02149ed21a86c423859cbe18dc7ed69845cace50e52b40a5" +checksum = "93aae7a4192245f70fe75dd9157fc7b4a5bf53e88d30bd4396f7d8f9284d5acc" dependencies = [ - "bitflags", + "clap_builder", "clap_derive", - "clap_lex", - "is-terminal", "once_cell", +] + +[[package]] +name = "clap_builder" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f423e341edefb78c9caba2d9c7f7687d0e72e89df3ce3394554754393ac3990" +dependencies = [ + "anstream", + "anstyle", + "bitflags", + "clap_lex", "strsim", - "termcolor", ] [[package]] name = "clap_derive" -version = "4.1.8" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44bec8e5c9d09e439c4335b1af0abaab56dcf3b94999a936e1bb47b9134288f0" +checksum = "191d9573962933b4027f932c600cd252ce27a8ad5979418fe78e43c07996f27b" dependencies = [ "heck", - "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 2.0.16", ] [[package]] name = "clap_lex" -version = "0.3.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "350b9cf31731f9957399229e9b2adc51eeabdfbe9d71d9a0552275fd12710d09" -dependencies = [ - "os_str_bytes", -] +checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "concurrent-queue" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c278839b831783b70278b14df4d45e1beb1aad306c07bb796637de9a0e323e8e" +checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" dependencies = [ "crossbeam-utils", ] [[package]] name = "console" -version = "0.15.5" +version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d79fbe8970a77e3e34151cc13d3b3e248aa0faaecb9f6091fa07ebefe5ad60" +checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" dependencies = [ "encode_unicode", "lazy_static", "libc", "unicode-width", - "windows-sys 0.42.0", + "windows-sys 0.45.0", ] [[package]] @@ -451,9 +544,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.5" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" dependencies = [ "libc", ] @@ -493,7 +586,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" dependencies = [ "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -513,9 +606,9 @@ dependencies = [ [[package]] name = "curl-sys" -version = "0.4.60+curl-7.88.1" +version = "0.4.62+curl-8.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "717abe2cb465a5da6ce06617388a3980c9a2844196734bec8ccb8e575250f13f" +checksum = "274ef7ef7c1113c7611af49ce248a700afa1171045a1aaa40137030773f993b8" dependencies = [ "cc", "libc", @@ -534,39 +627,19 @@ checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "digest" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", ] -[[package]] -name = "dirs" -version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - [[package]] name = "encode_unicode" version = "0.3.6" @@ -575,9 +648,9 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "enumflags2" -version = "0.7.5" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e75d4cd21b95383444831539909fbb14b9dc3fdceb2a6f5d36577329a1f55ccb" +checksum = "c041f5090df68b32bcd905365fd51769c8b9d553fe87fde0b683534f10c01bd2" dependencies = [ "enumflags2_derive", "serde", @@ -585,24 +658,24 @@ dependencies = [ [[package]] name = "enumflags2_derive" -version = "0.7.4" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f58dc3c5e468259f19f2d46304a6b28f1c3d034442e14b322d2b850e36f6d5ae" +checksum = "5e9a1f9f7d83e59740248a6e14ecf93929ade55027844dfcea78beafccc15745" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.16", ] [[package]] name = "errno" -version = "0.2.8" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" dependencies = [ "errno-dragonfly", "libc", - "winapi", + "windows-sys 0.48.0", ] [[package]] @@ -642,9 +715,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.25" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" +checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" dependencies = [ "crc32fast", "miniz_oxide", @@ -671,9 +744,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "531ac96c6ff5fd7c62263c5e3c67a603af4fcaee2e1a0ae5565ba3a11e69e549" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" dependencies = [ "futures-channel", "futures-core", @@ -686,9 +759,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "164713a5a0dcc3e7b4b1ed7d3b433cabc18025386f9339346e8daf15963cf7ac" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", "futures-sink", @@ -696,15 +769,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86d7a0c1aa76363dac491de0ee99faf6941128376f1cf96f07db7603b7de69dd" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" [[package]] name = "futures-executor" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1997dd9df74cdac935c76252744c1ed5794fac083242ea4fe77ef3ed60ba0f83" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" dependencies = [ "futures-core", "futures-task", @@ -713,15 +786,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89d422fa3cbe3b40dca574ab087abb5bc98258ea57eea3fd6f1fa7162c778b91" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" [[package]] name = "futures-lite" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" dependencies = [ "fastrand", "futures-core", @@ -734,32 +807,32 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eb14ed937631bd8b8b8977f2c198443447a8355b6e3ca599f38c975e5a963b6" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.16", ] [[package]] name = "futures-sink" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec93083a4aecafb2a80a885c9de1f0ccae9dbd32c2bb54b0c3a65690e0b8d2f2" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" [[package]] name = "futures-task" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd65540d33b37b16542a0438c12e6aeead10d4ac5d05bd3f805b8f35ab592879" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" [[package]] name = "futures-util" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ef6b17e481503ec85211fed8f39d1970f128935ca1f814cd32ac4a6842e84ab" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures-channel", "futures-core", @@ -775,9 +848,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.6" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -785,9 +858,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" dependencies = [ "cfg-if", "libc", @@ -842,9 +915,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.9.2" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown", @@ -858,7 +931,7 @@ checksum = "cef509aa9bc73864d6756f0d34d35504af3cf0844373afe9b8669a5b8005a729" dependencies = [ "console", "number_prefix", - "portable-atomic", + "portable-atomic 0.3.20", "unicode-width", ] @@ -873,24 +946,25 @@ dependencies = [ [[package]] name = "io-lifetimes" -version = "1.0.6" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfa919a82ea574332e2de6e74b4c36e74d41982b335080fa59d4ef31be20fdf3" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ + "hermit-abi", "libc", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] name = "is-terminal" -version = "0.4.4" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b6b32576413a8e69b90e952e4a026476040d81017b80445deda5f2d3921857" +checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" dependencies = [ "hermit-abi", "io-lifetimes", "rustix", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -910,9 +984,9 @@ checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "js-sys" -version = "0.3.61" +version = "0.3.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790" dependencies = [ "wasm-bindgen", ] @@ -961,15 +1035,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.140" +version = "0.2.144" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" +checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" [[package]] name = "libz-sys" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" +checksum = "56ee889ecc9568871456d42f603d6a0ce59ff328d291063a45cbdf0036baf6db" dependencies = [ "cc", "libc", @@ -979,9 +1053,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.1.4" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "lock_api" @@ -1026,9 +1100,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.6.2" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" dependencies = [ "adler", ] @@ -1043,7 +1117,6 @@ dependencies = [ "cfg-if", "libc", "memoffset", - "pin-utils", "static_assertions", ] @@ -1159,11 +1232,10 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.80" +version = "0.9.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7" +checksum = "8e17f59264b2809d77ae94f0e1ebabc434773f370d6ca667bd223ea10e06cc7e" dependencies = [ - "autocfg", "cc", "libc", "pkg-config", @@ -1180,17 +1252,11 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "os_str_bytes" -version = "6.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" - [[package]] name = "parking" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" +checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" [[package]] name = "parking_lot" @@ -1210,7 +1276,7 @@ checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.2.16", "smallvec", "windows-sys 0.45.0", ] @@ -1282,15 +1348,15 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "polling" -version = "2.6.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e1f879b2998099c2d69ab9605d145d5b661195627eccc680002c4918a7fb6fa" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" dependencies = [ "autocfg", "bitflags", @@ -1299,14 +1365,23 @@ dependencies = [ "libc", "log", "pin-project-lite", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] name = "portable-atomic" -version = "0.3.19" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26f6a7b87c2e435a3241addceeeff740ff8b7e76b74c13bf9acb17fa454ea00b" +checksum = "e30165d31df606f5726b090ec7592c308a0eaf61721ff64c9a3018e344a8753e" +dependencies = [ + "portable-atomic 1.3.2", +] + +[[package]] +name = "portable-atomic" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc59d1bcc64fc5d021d67521f818db868368028108d37f0e98d74e33f68297b5" [[package]] name = "ppv-lite86" @@ -1324,35 +1399,11 @@ dependencies = [ "toml_edit", ] -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - [[package]] name = "proc-macro2" -version = "1.0.51" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" +checksum = "fa1fb82fc0c281dd9671101b66b771ebbe1eaf967b96ac8740dcba4b70005ca8" dependencies = [ "unicode-ident", ] @@ -1369,9 +1420,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.23" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" dependencies = [ "proc-macro2", ] @@ -1416,21 +1467,19 @@ dependencies = [ ] [[package]] -name = "redox_users" -version = "0.4.3" +name = "redox_syscall" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ - "getrandom", - "redox_syscall", - "thiserror", + "bitflags", ] [[package]] name = "regex" -version = "1.7.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" +checksum = "d1a59b5d8e97dee33696bf13c5ba8ab85341c002922fba050069326b9c498974" dependencies = [ "aho-corasick", "memchr", @@ -1439,22 +1488,22 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.28" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" +checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" [[package]] name = "rustix" -version = "0.36.9" +version = "0.37.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd5c6ff11fecd55b40746d1995a02f2eb375bf8c00d192d521ee09f42bef37bc" +checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" dependencies = [ "bitflags", "errno", "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -1480,29 +1529,29 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "serde" -version = "1.0.154" +version = "1.0.163" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cdd151213925e7f1ab45a9bbfb129316bd00799784b174b7cc7bcd16961c49e" +checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.154" +version = "1.0.163" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fc80d722935453bcafdc2c9a73cd6fac4dc1938f0346035d84bf99fa9e33217" +checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.16", ] [[package]] name = "serde_json" -version = "1.0.94" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea" +checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" dependencies = [ "itoa", "ryu", @@ -1511,20 +1560,20 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395627de918015623b32e7669714206363a7fc00382bf477e72c1f7533e8eafc" +checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.16", ] [[package]] name = "serde_yaml" -version = "0.9.19" +version = "0.9.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f82e6c8c047aa50a7328632d067bcae6ef38772a79e28daf32f735e0e4f3dd10" +checksum = "d9d684e3ec7de3bf5466b32bd75303ac16f0736426e5a4e0d6e489559ce1249c" dependencies = [ "indexmap", "itoa", @@ -1544,6 +1593,25 @@ dependencies = [ "digest", ] +[[package]] +name = "signal-hook" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + [[package]] name = "siphasher" version = "0.3.10" @@ -1599,52 +1667,54 @@ dependencies = [ ] [[package]] -name = "tempfile" -version = "3.4.0" +name = "syn" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95" +checksum = "a6f671d4b5ffdb8eadec19c0ae67fe2639df8684bd7bc4b83d986b8db549cf01" dependencies = [ - "cfg-if", - "fastrand", - "redox_syscall", - "rustix", - "windows-sys 0.42.0", + "proc-macro2", + "quote", + "unicode-ident", ] [[package]] -name = "termcolor" -version = "1.2.0" +name = "tempfile" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" dependencies = [ - "winapi-util", + "cfg-if", + "fastrand", + "redox_syscall 0.3.5", + "rustix", + "windows-sys 0.45.0", ] [[package]] name = "thiserror" -version = "1.0.39" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5ab016db510546d856297882807df8da66a16fb8c4101cb8b30054b0d5b2d9c" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.39" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.16", ] [[package]] name = "time" -version = "0.3.20" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" +checksum = "8f3403384eaacbca9923fa06940178ac13e4edb725486d70e8e15881d0c836cc" dependencies = [ "serde", "time-core", @@ -1653,15 +1723,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" +checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" [[package]] name = "time-macros" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" +checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" dependencies = [ "time-core", ] @@ -1683,15 +1753,15 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "toml_datetime" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" +checksum = "5a76a9312f5ba4c2dec6b9161fdf25d87ad8a09256ccea5a556fef03c706a10f" [[package]] name = "toml_edit" -version = "0.19.4" +version = "0.19.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a1eb0622d28f4b9c90adc4ea4b2b46b47663fde9ac5fafcb14a1369d5508825" +checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13" dependencies = [ "indexmap", "toml_datetime", @@ -1712,20 +1782,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.16", ] [[package]] name = "tracing-core" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" dependencies = [ "once_cell", ] @@ -1748,15 +1818,15 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.11" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524b68aca1d05e03fdf03fcdce2c6c94b6daf6d16861ddaa7e4f2b6638a9052c" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" [[package]] name = "unicode-normalization" @@ -1781,9 +1851,9 @@ checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] name = "unsafe-libyaml" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad2024452afd3874bf539695e04af6732ba06517424dbf958fdb16a01f3bef6c" +checksum = "1865806a559042e51ab5414598446a5871b561d21b6764f2eabb0dd481d880a6" [[package]] name = "url" @@ -1796,11 +1866,20 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "uuid" -version = "1.3.0" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1674845326ee10d37ca60470760d4288a6f80f304007d92e5c53bab78c9cfd79" +checksum = "345444e32442451b267fc254ae85a209c64be56d2890e601a0c37ff0c3c5ecd2" +dependencies = [ + "getrandom", +] [[package]] name = "value-bag" @@ -1838,9 +1917,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.84" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1848,24 +1927,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.84" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.16", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.34" +version = "0.4.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" +checksum = "2d1985d03709c53167ce907ff394f5316aa22cb4e12761295c5dc57dacb6297e" dependencies = [ "cfg-if", "js-sys", @@ -1875,9 +1954,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.84" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1885,28 +1964,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.84" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.16", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.84" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" +checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" [[package]] name = "web-sys" -version = "0.3.61" +version = "0.3.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2" dependencies = [ "js-sys", "wasm-bindgen", @@ -1928,15 +2007,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -1949,13 +2019,13 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", ] [[package]] @@ -1964,92 +2034,168 @@ version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ - "windows-targets", + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.0", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", ] [[package]] name = "windows-targets" -version = "0.42.1" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.1" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" -version = "0.42.1" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" [[package]] name = "windows_i686_gnu" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" [[package]] name = "windows_i686_msvc" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" [[package]] name = "windows_x86_64_gnu" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.1" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" [[package]] name = "windows_x86_64_msvc" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "winnow" -version = "0.3.5" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee7b2c67f962bf5042bfd8b6a916178df33a26eec343ae064cb8e069f638fa6f" +checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28" dependencies = [ "memchr", ] +[[package]] +name = "xdg-home" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2769203cd13a0c6015d515be729c526d041e9cf2c0cc478d57faee85f40c6dcd" +dependencies = [ + "nix", + "winapi", +] + [[package]] name = "zbus" -version = "3.11.0" +version = "3.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20aae5dd5b051971cd2f49f9f3b860e57b2b495ba5ba254eaec42d34ede57e97" +checksum = "6c3d77c9966c28321f1907f0b6c5a5561189d1f7311eea6d94180c6be9daab29" dependencies = [ "async-broadcast", "async-executor", "async-fs", "async-io", "async-lock", + "async-process", "async-recursion", "async-task", "async-trait", "byteorder", "derivative", - "dirs", "enumflags2", "event-listener", "futures-core", @@ -2067,6 +2213,7 @@ dependencies = [ "tracing", "uds_windows", "winapi", + "xdg-home", "zbus_macros", "zbus_names", "zvariant", @@ -2074,23 +2221,24 @@ dependencies = [ [[package]] name = "zbus_macros" -version = "3.11.0" +version = "3.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9264b3a1bcf5503d4e0348b6e7efe1da58d4f92a913c15ed9e63b52de85faaa1" +checksum = "f6e341d12edaff644e539ccbbf7f161601294c9a84ed3d7e015da33155b435af" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", "regex", - "syn", + "syn 1.0.109", + "winnow", "zvariant_utils", ] [[package]] name = "zbus_names" -version = "2.5.0" +version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f34f314916bd89bdb9934154627fab152f4f28acdda03e7c4c68181b214fe7e3" +checksum = "82441e6033be0a741157a72951a3e4957d519698f3a824439cc131c5ba77ac2a" dependencies = [ "serde", "static_assertions", @@ -2099,9 +2247,9 @@ dependencies = [ [[package]] name = "zvariant" -version = "3.12.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46fe4914a985446d6fd287019b5fceccce38303d71407d9e6e711d44954a05d8" +checksum = "622cc473f10cef1b0d73b7b34a266be30ebdcfaea40ec297dd8cbda088f9f93c" dependencies = [ "byteorder", "enumflags2", @@ -2113,24 +2261,24 @@ dependencies = [ [[package]] name = "zvariant_derive" -version = "3.12.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34c20260af4b28b3275d6676c7e2a6be0d4332e8e0aba4616d34007fd84e462a" +checksum = "5d9c1b57352c25b778257c661f3c4744b7cefb7fc09dd46909a153cce7773da2" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "zvariant_utils", ] [[package]] name = "zvariant_utils" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b22993dbc4d128a17a3b6c92f1c63872dd67198537ee728d8b5d7c40640a8b" +checksum = "7234f0d811589db492d16893e3f21e8e2fd282e6d01b0cddee310322062cc200" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 4143977ed6..636ad6328e 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,8 +1,9 @@ [workspace] members = [ - "agama-lib", "agama-cli", + "agama-dbus-server", "agama-derive", + "agama-lib", "agama-locale-data", - "agama-dbus-server" + "agama-network" ] diff --git a/rust/agama-dbus-server/Cargo.toml b/rust/agama-dbus-server/Cargo.toml index 6a38539f7c..b478b0d727 100644 --- a/rust/agama-dbus-server/Cargo.toml +++ b/rust/agama-dbus-server/Cargo.toml @@ -8,6 +8,8 @@ edition = "2021" [dependencies] anyhow = "1.0" agama-locale-data = { path="../agama-locale-data" } +agama-network = { path="../agama-network" } +agama-lib = { path="../agama-lib" } zbus = "3.7.0" zbus_macros = "3.7.0" async-std = { version = "1.12.0", features = ["attributes"]} diff --git a/rust/agama-dbus-server/src/locale.rs b/rust/agama-dbus-server/src/locale.rs index 06ecfa85b7..ec0bcef66e 100644 --- a/rust/agama-dbus-server/src/locale.rs +++ b/rust/agama-dbus-server/src/locale.rs @@ -1,9 +1,10 @@ use crate::error::Error; +use agama_lib::connection_to; use anyhow::Context; use std::process::Command; -use zbus::{dbus_interface, Connection, ConnectionBuilder}; +use zbus::{dbus_interface, Connection}; -pub struct Locale { +pub struct LocaleService { locales: Vec, keymap: String, timezone_id: String, @@ -11,7 +12,7 @@ pub struct Locale { } #[dbus_interface(name = "org.opensuse.Agama.Locale1")] -impl Locale { +impl LocaleService { // Can be `async` as well. /// get labels for given locale. The first pair is english language and territory /// and second one is localized one to target language from locale. @@ -25,9 +26,11 @@ impl Locale { for locale in self.supported_locales.as_slice() { let (loc_language, loc_territory) = agama_locale_data::parse_locale(locale.as_str())?; - let language = languages.find_by_id(loc_language) + let language = languages + .find_by_id(loc_language) .context("language for passed locale not found")?; - let territory = territories.find_by_id(loc_territory) + let territory = territories + .find_by_id(loc_territory) .context("territory for passed locale not found")?; let default_ret = ( @@ -65,7 +68,9 @@ impl Locale { fn set_locales(&mut self, locales: Vec) -> zbus::fdo::Result<()> { for loc in &locales { if !self.supported_locales.contains(loc) { - return Err(zbus::fdo::Error::Failed(format!("Unsupported locale value '{loc}'"))) + return Err(zbus::fdo::Error::Failed(format!( + "Unsupported locale value '{loc}'" + ))); } } self.locales = locales; @@ -99,22 +104,28 @@ impl Locale { } */ - #[dbus_interface(name="ListVConsoleKeyboards")] + #[dbus_interface(name = "ListVConsoleKeyboards")] fn list_keyboards(&self) -> Result, Error> { let res = agama_locale_data::get_key_maps()?; Ok(res) } - #[dbus_interface(property, name="VConsoleKeyboard")] + #[dbus_interface(property, name = "VConsoleKeyboard")] fn keymap(&self) -> &str { return &self.keymap.as_str(); } - #[dbus_interface(property, name="VConsoleKeyboard")] + #[dbus_interface(property, name = "VConsoleKeyboard")] fn set_keymap(&mut self, keyboard: &str) -> Result<(), zbus::fdo::Error> { - let exist = agama_locale_data::get_key_maps().unwrap().iter().find(|&k| k == keyboard).is_some(); + let exist = agama_locale_data::get_key_maps() + .unwrap() + .iter() + .find(|&k| k == keyboard) + .is_some(); if !exist { - return Err(zbus::fdo::Error::Failed("Invalid keyboard value".to_string())) + return Err(zbus::fdo::Error::Failed( + "Invalid keyboard value".to_string(), + )); } self.keymap = keyboard.to_string(); Ok(()) @@ -134,7 +145,8 @@ impl Locale { } #[dbus_interface(property)] - fn set_timezone(&mut self, timezone: &str) -> Result<(), zbus::fdo::Error> { // NOTE: cannot use crate::Error as property expect this one + fn set_timezone(&mut self, timezone: &str) -> Result<(), zbus::fdo::Error> { + // NOTE: cannot use crate::Error as property expect this one self.timezone_id = timezone.to_string(); Ok(()) } @@ -143,7 +155,12 @@ impl Locale { fn commit(&mut self) -> Result<(), Error> { const ROOT: &str = "/mnt"; Command::new("/usr/bin/systemd-firstboot") - .args(["root", ROOT, "--locale", self.locales.first().context("missing locale")?.as_str()]) + .args([ + "root", + ROOT, + "--locale", + self.locales.first().context("missing locale")?.as_str(), + ]) .status() .context("Failed to execute systemd-firstboot")?; Command::new("/usr/bin/systemd-firstboot") @@ -159,10 +176,9 @@ impl Locale { } } - -impl Locale { - fn new() -> Locale { - Locale { +impl LocaleService { + fn new() -> Self { + Self { locales: vec!["en_US.UTF-8".to_string()], keymap: "us".to_string(), timezone_id: "America/Los_Angeles".to_string(), @@ -170,30 +186,22 @@ impl Locale { } } - pub async fn start_service() -> Result> { - const ADDRESS : &str = "unix:path=/run/agama/bus"; + pub async fn start(address: &str) -> Result> { const SERVICE_NAME: &str = "org.opensuse.Agama.Locale1"; const SERVICE_PATH: &str = "/org/opensuse/Agama/Locale1"; // First connect to the Agama bus, then serve our API, // for better error reporting. - let conn = ConnectionBuilder::address(ADDRESS)? - .build() - .await - .context(format!("Connecting to the Agama bus at {ADDRESS}"))?; + let connection = connection_to(address).await?; // When serving, request the service name _after_ exposing the main object - let locale = Locale::new(); - conn - .object_server() - .at(SERVICE_PATH, locale) - .await?; - conn + let locale = Self::new(); + connection.object_server().at(SERVICE_PATH, locale).await?; + connection .request_name(SERVICE_NAME) .await .context(format!("Requesting name {SERVICE_NAME}"))?; - Ok(conn) + Ok(connection) } } - diff --git a/rust/agama-dbus-server/src/main.rs b/rust/agama-dbus-server/src/main.rs index 39aa557235..5b0e7f419b 100644 --- a/rust/agama-dbus-server/src/main.rs +++ b/rust/agama-dbus-server/src/main.rs @@ -1,11 +1,16 @@ pub mod error; pub mod locale; +use agama_network::NetworkService; use std::future::pending; +const ADDRESS: &str = "unix:path=/run/agama/bus"; + #[async_std::main] async fn main() -> Result<(), Box> { - let _con = crate::locale::Locale::start_service().await?; + // When adding more services here, the order might be important. + let _con = crate::locale::LocaleService::start(ADDRESS).await?; + NetworkService::start(ADDRESS).await?; // Do other things or go to wait forever pending::<()>().await; diff --git a/rust/agama-lib/src/dbus.rs b/rust/agama-lib/src/dbus.rs new file mode 100644 index 0000000000..863b42cb65 --- /dev/null +++ b/rust/agama-lib/src/dbus.rs @@ -0,0 +1,7 @@ +use std::collections::HashMap; +use zbus::zvariant; + +/// Nested hash to send to D-Bus. +pub type NestedHash<'a> = HashMap<&'a str, HashMap<&'a str, zvariant::Value<'a>>>; +/// Nested hash as it comes from D-Bus. +pub type OwnedNestedHash = HashMap>; diff --git a/rust/agama-lib/src/error.rs b/rust/agama-lib/src/error.rs index 796039e98f..da828763c0 100644 --- a/rust/agama-lib/src/error.rs +++ b/rust/agama-lib/src/error.rs @@ -33,5 +33,5 @@ pub enum WrongParameter { #[error("Unknown product '{0}'. Available products: '{1:?}'")] UnknownProduct(String, Vec), #[error("Wrong user parameters: '{0:?}'")] - WrongUser(Vec) + WrongUser(Vec), } diff --git a/rust/agama-lib/src/lib.rs b/rust/agama-lib/src/lib.rs index ab085a5f7b..8240beba0e 100644 --- a/rust/agama-lib/src/lib.rs +++ b/rust/agama-lib/src/lib.rs @@ -7,6 +7,7 @@ pub mod software; pub mod storage; pub mod users; // TODO: maybe expose only clients when we have it? +pub mod dbus; pub mod progress; pub mod proxies; mod store; @@ -15,12 +16,16 @@ pub use store::Store; use crate::error::ServiceError; use anyhow::Context; +const ADDRESS: &str = "unix:path=/run/agama/bus"; + pub async fn connection() -> Result { - const PATH : &str = "/run/agama/bus"; - let address : String = format!("unix:path={PATH}"); - let conn = zbus::ConnectionBuilder::address(address.as_str())? + connection_to(ADDRESS).await +} + +pub async fn connection_to(address: &str) -> Result { + let connection = zbus::ConnectionBuilder::address(address)? .build() .await - .with_context(|| format!("Connecting to D-Bus address {}", address))?; - Ok(conn) + .context(format!("Connecting to Agama bus at {ADDRESS}"))?; + Ok(connection) } diff --git a/rust/agama-lib/src/proxies.rs b/rust/agama-lib/src/proxies.rs index 476b052218..17301cb1fe 100644 --- a/rust/agama-lib/src/proxies.rs +++ b/rust/agama-lib/src/proxies.rs @@ -1,18 +1,6 @@ -//! # DBus interface proxies for: `org.opensuse.Agama*.**.*` +//! D-Bus interface proxies for: `org.opensuse.Agama*.**.*` //! //! This code was generated by `zbus-xmlgen` `3.1.0` from DBus introspection data.`. -//! -//! -//! These DBus objects implements -//! [standard DBus interfaces](https://dbus.freedesktop.org/doc/dbus-specification.html), -//! (`org.freedesktop.DBus.*`) for which the following zbus proxies can be used: -//! -//! * [`zbus::fdo::PropertiesProxy`] -//! * [`zbus::fdo::IntrospectableProxy`] -//! -//! …consequently `zbus-xmlgen` did not generate code for the above interfaces. -//! Also some proxies can be used against multiple services when they share interface. - use zbus::dbus_proxy; /// Progress1Proxy can be used also with Software and Storage object. diff --git a/rust/agama-network/Cargo.toml b/rust/agama-network/Cargo.toml new file mode 100644 index 0000000000..3aad2f4ce6 --- /dev/null +++ b/rust/agama-network/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "agama-network" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +zbus = "3.12.0" +agama-lib = { path="../agama-lib" } +async-std = { version = "1.12.0", features = ["attributes"] } +thiserror = "1.0.40" +uuid = { version = "1.3.3", features = ["v4"] } +parking_lot = "0.12.1" diff --git a/rust/agama-network/src/action.rs b/rust/agama-network/src/action.rs new file mode 100644 index 0000000000..7779bebc00 --- /dev/null +++ b/rust/agama-network/src/action.rs @@ -0,0 +1,18 @@ +use crate::model::{Connection, DeviceType}; +use uuid::Uuid; + +/// Networking actions, like adding, updating or removing connections. +/// +/// These actions are meant to be processed by [crate::system::NetworkSystem], updating the model +/// and the D-Bus tree as needed. +#[derive(Debug)] +pub enum Action { + /// Add a new connection with the given name and type. + AddConnection(String, DeviceType), + /// Update a connection (replacing the old one). + UpdateConnection(Connection), + /// Remove the connection with the given Uuid. + RemoveConnection(Uuid), + /// Apply the current configuration. + Apply, +} diff --git a/rust/agama-network/src/adapter.rs b/rust/agama-network/src/adapter.rs new file mode 100644 index 0000000000..f3040d06e9 --- /dev/null +++ b/rust/agama-network/src/adapter.rs @@ -0,0 +1,8 @@ +use crate::NetworkState; +use std::error::Error; + +/// A trait for the ability to read/write from/to a network service +pub trait Adapter { + fn read(&self) -> Result>; + fn write(&self, network: &NetworkState) -> Result<(), Box>; +} diff --git a/rust/agama-network/src/dbus.rs b/rust/agama-network/src/dbus.rs new file mode 100644 index 0000000000..ba95f4c9ac --- /dev/null +++ b/rust/agama-network/src/dbus.rs @@ -0,0 +1,11 @@ +//! D-Bus service and interfaces. +//! +//! This module contains a [D-Bus network service](NetworkService) which expose the network +//! configuration for Agama. + +mod interfaces; +pub mod service; +mod tree; + +pub use service::NetworkService; +pub(crate) use tree::{ObjectsRegistry, Tree}; diff --git a/rust/agama-network/src/dbus/interfaces.rs b/rust/agama-network/src/dbus/interfaces.rs new file mode 100644 index 0000000000..45de5f4146 --- /dev/null +++ b/rust/agama-network/src/dbus/interfaces.rs @@ -0,0 +1,406 @@ +//! Network D-Bus interfaces. +//! +//! This module contains the set of D-Bus interfaces that are exposed by [D-Bus network +//! service](crate::NetworkService). +use super::ObjectsRegistry; +use crate::{ + action::Action, + error::NetworkStateError, + model::{Connection as NetworkConnection, Device as NetworkDevice, WirelessConnection}, +}; +use parking_lot::{MappedMutexGuard, Mutex, MutexGuard}; +use std::{ + net::{AddrParseError, Ipv4Addr}, + sync::{mpsc::Sender, Arc}, +}; +use uuid::Uuid; +use zbus::{dbus_interface, zvariant::ObjectPath}; + +/// D-Bus interface for the network devices collection +/// +/// It offers an API to query the devices collection. +pub struct Devices { + objects: Arc>, +} + +impl Devices { + /// Creates a Devices interface object. + /// + /// * `objects`: Objects paths registry. + pub fn new(objects: Arc>) -> Self { + Self { objects } + } +} + +#[dbus_interface(name = "org.opensuse.Agama.Network1.Devices")] +impl Devices { + /// Returns the D-Bus paths of the network devices. + pub fn get_devices(&self) -> Vec { + let objects = self.objects.lock(); + objects + .devices_paths() + .iter() + .filter_map(|c| ObjectPath::try_from(c.clone()).ok()) + .collect() + } +} + +/// D-Bus interface for a network device +/// +/// It offers an API to query basic networking devices information (e.g., the name). +pub struct Device { + device: NetworkDevice, +} + +impl Device { + /// Creates an interface object. + /// + /// * `device`: network device. + pub fn new(device: NetworkDevice) -> Self { + Self { device } + } +} + +#[dbus_interface(name = "org.opensuse.Agama.Network1.Device")] +impl Device { + #[dbus_interface(property)] + pub fn name(&self) -> &str { + &self.device.name + } + + #[dbus_interface(property, name = "Type")] + pub fn device_type(&self) -> u8 { + self.device.type_ as u8 + } +} + +/// D-Bus interface for the set of connections. +/// +/// It offers an API to query the connections collection. +pub struct Connections { + actions: Arc>>, + objects: Arc>, +} + +impl Connections { + /// Creates a Connections interface object. + /// + /// * `objects`: Objects paths registry. + pub fn new(objects: Arc>, actions: Sender) -> Self { + Self { + objects, + actions: Arc::new(Mutex::new(actions)), + } + } +} + +#[dbus_interface(name = "org.opensuse.Agama.Network1.Connections")] +impl Connections { + /// Returns the D-Bus paths of the network connections. + pub fn get_connections(&self) -> Vec { + let objects = self.objects.lock(); + objects + .connections_paths() + .iter() + .filter_map(|c| ObjectPath::try_from(c.clone()).ok()) + .collect() + } + + /// Adds a new network connection. + /// + /// * `name`: connection name. + /// * `ty`: connection type (see [crate::model::DeviceType]). + pub async fn add_connection(&mut self, name: String, ty: u8) -> zbus::fdo::Result<()> { + let actions = self.actions.lock(); + actions + .send(Action::AddConnection(name, ty.try_into()?)) + .unwrap(); + Ok(()) + } + + /// Removes a network connection. + /// + /// * `uuid`: connection UUID.. + pub async fn remove_connection(&mut self, uuid: &str) -> zbus::fdo::Result<()> { + let actions = self.actions.lock(); + let uuid = + Uuid::parse_str(uuid).map_err(|_| NetworkStateError::InvalidUuid(uuid.to_string()))?; + actions.send(Action::RemoveConnection(uuid)).unwrap(); + Ok(()) + } + + /// Applies the network configuration. + /// + /// It includes adding, updating and removing connections as needed. + pub async fn apply(&self) -> zbus::fdo::Result<()> { + let actions = self.actions.lock(); + actions.send(Action::Apply).unwrap(); + Ok(()) + } +} + +/// D-Bus interface for a network connection +/// +/// It offers an API to query a connection. +pub struct Connection { + connection: Arc>, +} + +impl Connection { + /// Creates a Connection interface object. + /// + /// * `connection`: connection to expose over D-Bus. + pub fn new(connection: Arc>) -> Self { + Self { connection } + } + + /// Returns the underlying connection. + fn get_connection(&self) -> MutexGuard { + self.connection.lock() + } +} + +#[dbus_interface(name = "org.opensuse.Agama.Network1.Connection")] +impl Connection { + #[dbus_interface(property)] + pub fn id(&self) -> String { + self.get_connection().id().to_string() + } + + #[dbus_interface(property, name = "UUID")] + pub fn uuid(&self) -> String { + self.get_connection().uuid().to_string() + } +} + +/// D-Bus interface for IPv4 settings +pub struct Ipv4 { + actions: Arc>>, + connection: Arc>, +} + +impl Ipv4 { + /// Creates an IPv4 interface object. + /// + /// * `actions`: sending-half of a channel to send actions. + /// * `connection`: connection to expose over D-Bus. + pub fn new(actions: Sender, connection: Arc>) -> Self { + Self { + actions: Arc::new(Mutex::new(actions)), + connection, + } + } + + /// Returns the underlying connection. + fn get_connection(&self) -> MutexGuard { + self.connection.lock() + } + + /// Updates the connection data in the NetworkSystem. + /// + /// * `connection`: Updated connection. + fn update_connection( + &self, + connection: MutexGuard, + ) -> zbus::fdo::Result<()> { + let actions = self.actions.lock(); + actions + .send(Action::UpdateConnection(connection.clone())) + .unwrap(); + Ok(()) + } +} + +#[dbus_interface(name = "org.opensuse.Agama.Network1.Connection.IPv4")] +impl Ipv4 { + #[dbus_interface(property)] + pub fn addresses(&self) -> Vec<(String, u32)> { + let connection = self.get_connection(); + connection + .ipv4() + .addresses + .iter() + .map(|(addr, prefix)| (addr.to_string(), *prefix)) + .collect() + } + + #[dbus_interface(property)] + pub fn set_addresses(&mut self, addresses: Vec<(String, u32)>) -> zbus::fdo::Result<()> { + let mut connection = self.get_connection(); + addresses + .iter() + .map(|(addr, prefix)| addr.parse::().map(|a| (a, *prefix))) + .collect::, AddrParseError>>() + .and_then(|parsed| Ok(connection.ipv4_mut().addresses = parsed)) + .map_err(|err| NetworkStateError::from(err))?; + self.update_connection(connection) + } + + #[dbus_interface(property)] + pub fn method(&self) -> u8 { + let connection = self.get_connection(); + connection.ipv4().method as u8 + } + + #[dbus_interface(property)] + pub fn set_method(&mut self, method: u8) -> zbus::fdo::Result<()> { + let mut connection = self.get_connection(); + connection.ipv4_mut().method = method.try_into()?; + self.update_connection(connection) + } + + #[dbus_interface(property)] + pub fn nameservers(&self) -> Vec { + let connection = self.get_connection(); + connection + .ipv4() + .nameservers + .iter() + .map(|a| a.to_string()) + .collect() + } + + #[dbus_interface(property)] + pub fn set_nameservers(&mut self, addresses: Vec) -> zbus::fdo::Result<()> { + let mut connection = self.get_connection(); + let ipv4 = connection.ipv4_mut(); + addresses + .iter() + .map(|addr| addr.parse::()) + .collect::, AddrParseError>>() + .and_then(|parsed| Ok(ipv4.nameservers = parsed)) + .map_err(|err| NetworkStateError::from(err))?; + self.update_connection(connection) + } + + #[dbus_interface(property)] + pub fn gateway(&self) -> String { + let connection = self.get_connection(); + match connection.ipv4().gateway { + Some(addr) => addr.to_string(), + None => "".to_string(), + } + } + + #[dbus_interface(property)] + pub fn set_gateway(&mut self, gateway: String) -> zbus::fdo::Result<()> { + let mut connection = self.get_connection(); + let ipv4 = connection.ipv4_mut(); + if gateway.is_empty() { + ipv4.gateway = None; + } else { + let parsed: Ipv4Addr = gateway + .parse() + .map_err(|err| NetworkStateError::from(err))?; + ipv4.gateway = Some(parsed); + } + self.update_connection(connection) + } +} +/// D-Bus interface for wireless settings +pub struct Wireless { + actions: Arc>>, + connection: Arc>, +} + +impl Wireless { + /// Creates a Wireless interface object. + /// + /// * `actions`: sending-half of a channel to send actions. + /// * `connection`: connection to expose over D-Bus. + pub fn new(actions: Sender, connection: Arc>) -> Self { + Self { + actions: Arc::new(Mutex::new(actions)), + connection, + } + } + + /// Gets the wireless connection. + /// + /// Beware that it crashes when it is not a wireless connection. + fn get_wireless(&self) -> MappedMutexGuard { + MutexGuard::map(self.connection.lock(), |c| match c { + NetworkConnection::Wireless(config) => config, + _ => panic!("Not a wireless network. This is most probably a bug."), + }) + } + + /// Updates the connection data in the NetworkSystem. + /// + /// * `connection`: Updated connection. + fn update_connection( + &self, + connection: MappedMutexGuard, + ) -> zbus::fdo::Result<()> { + let actions = self.actions.lock(); + let connection = NetworkConnection::Wireless(connection.clone()); + actions.send(Action::UpdateConnection(connection)).unwrap(); + Ok(()) + } +} + +#[dbus_interface(name = "org.opensuse.Agama.Network1.Connection.Wireless")] +impl Wireless { + #[dbus_interface(property, name = "SSID")] + pub fn ssid(&self) -> Vec { + let connection = self.get_wireless(); + connection.wireless.ssid.clone() + } + + #[dbus_interface(property, name = "SSID")] + pub fn set_ssid(&mut self, ssid: Vec) -> zbus::fdo::Result<()> { + let mut connection = self.get_wireless(); + connection.wireless.ssid = ssid; + self.update_connection(connection) + } + + #[dbus_interface(property)] + pub fn mode(&self) -> u8 { + let connection = self.get_wireless(); + connection.wireless.mode as u8 + } + + #[dbus_interface(property)] + pub fn set_mode(&mut self, mode: u8) -> zbus::fdo::Result<()> { + let mut connection = self.get_wireless(); + connection.wireless.mode = mode.try_into()?; + self.update_connection(connection) + } + + #[dbus_interface(property)] + pub fn password(&self) -> String { + let connection = self.get_wireless(); + connection + .wireless + .password + .clone() + .unwrap_or("".to_string()) + } + + #[dbus_interface(property)] + pub fn set_password(&mut self, password: String) -> zbus::fdo::Result<()> { + let mut connection = self.get_wireless(); + connection.wireless.password = if password.is_empty() { + None + } else { + Some(password) + }; + self.update_connection(connection) + } + + #[dbus_interface(property)] + pub fn security(&self) -> u8 { + let connection = self.get_wireless(); + connection.wireless.security as u8 + } + + #[dbus_interface(property)] + pub fn set_security(&mut self, security: &str) -> zbus::fdo::Result<()> { + let mut connection = self.get_wireless(); + connection.wireless.security = security + .try_into() + .map_err(|_| NetworkStateError::InvalidSecurityProtocol(security.to_string()))?; + self.update_connection(connection)?; + Ok(()) + } +} diff --git a/rust/agama-network/src/dbus/service.rs b/rust/agama-network/src/dbus/service.rs new file mode 100644 index 0000000000..5ffe35d8fe --- /dev/null +++ b/rust/agama-network/src/dbus/service.rs @@ -0,0 +1,39 @@ +//! Network D-Bus service. +//! +//! This module defines a D-Bus service which exposes Agama's network configuration. +use crate::NetworkSystem; +use agama_lib::connection_to; +use std::{error::Error, thread}; + +/// Represents the Agama networking D-Bus service. +/// +/// It is responsible for starting the [NetworkSystem] on a different thread. +pub struct NetworkService; + +impl NetworkService { + /// Starts listening and dispatching events on the D-Bus connection. + pub async fn start(address: &str) -> Result<(), Box> { + const SERVICE_NAME: &str = "org.opensuse.Agama.Network1"; + + let connection = connection_to(address).await?; + let mut network = NetworkSystem::from_network_manager(connection.clone()) + .await + .expect("Could not read network state"); + + thread::spawn(move || { + async_std::task::block_on(async { + network + .setup() + .await + .expect("Could not set up the D-Bus tree"); + connection + .request_name(SERVICE_NAME) + .await + .expect(&format!("Could not request name {SERVICE_NAME}")); + + network.listen().await; + }) + }); + Ok(()) + } +} diff --git a/rust/agama-network/src/dbus/tree.rs b/rust/agama-network/src/dbus/tree.rs new file mode 100644 index 0000000000..4dc17404c8 --- /dev/null +++ b/rust/agama-network/src/dbus/tree.rs @@ -0,0 +1,233 @@ +use agama_lib::error::ServiceError; +use parking_lot::Mutex; +use uuid::Uuid; + +use crate::{action::Action, dbus::interfaces, model::*}; +use std::collections::HashMap; +use std::sync::mpsc::Sender; +use std::sync::Arc; + +const CONNECTIONS_PATH: &str = "/org/opensuse/Agama/Network1/connections"; +const DEVICES_PATH: &str = "/org/opensuse/Agama/Network1/devices"; + +/// Handle the objects in the D-Bus tree for the network state +pub struct Tree { + connection: zbus::Connection, + actions: Sender, + objects: Arc>, +} + +impl Tree { + /// Creates a new tree handler. + /// + /// * `connection`: D-Bus connection to use. + /// * `actions`: sending-half of a channel to send actions. + pub fn new(connection: zbus::Connection, actions: Sender) -> Self { + Self { + connection, + actions, + objects: Default::default(), + } + } + + /// Refreshes the list of connections. + /// + /// TODO: re-creating the tree is kind of brute-force and it sends signals about + /// adding/removing interfaces. We should add/update/delete objects as needed. + /// + /// * `connections`: list of connections. + pub async fn set_connections( + &self, + connections: &Vec, + ) -> Result<(), ServiceError> { + self.remove_connections().await?; + self.add_connections(connections).await?; + Ok(()) + } + + /// Refreshes the list of devices. + /// + /// * `devices`: list of devices. + pub async fn set_devices(&mut self, devices: &Vec) -> Result<(), ServiceError> { + self.remove_devices().await?; + self.add_devices(devices).await?; + Ok(()) + } + + /// Adds devices to the D-Bus tree. + /// + /// * `devices`: list of devices. + pub async fn add_devices(&mut self, devices: &Vec) -> Result<(), ServiceError> { + for (i, dev) in devices.iter().enumerate() { + let path = format!("{}/{}", DEVICES_PATH, i); + self.add_interface(&path, interfaces::Device::new(dev.clone())) + .await?; + let mut objects = self.objects.lock(); + objects.register_device(&dev.name, &path); + } + + self.add_interface( + DEVICES_PATH, + interfaces::Devices::new(Arc::clone(&self.objects)), + ) + .await?; + + Ok(()) + } + + /// Adds a connection to the D-Bus tree. + /// + /// * `connection`: connection to add. + pub async fn add_connection(&self, conn: &Connection) -> Result<(), ServiceError> { + let mut objects = self.objects.lock(); + + let path = format!("{}/{}", CONNECTIONS_PATH, objects.connections.len()); + let cloned = Arc::new(Mutex::new(conn.clone())); + self.add_interface(&path, interfaces::Connection::new(Arc::clone(&cloned))) + .await?; + + self.add_interface( + &path, + interfaces::Ipv4::new(self.actions.clone(), Arc::clone(&cloned)), + ) + .await?; + + if let Connection::Wireless(_) = conn { + self.add_interface( + &path, + interfaces::Wireless::new(self.actions.clone(), Arc::clone(&cloned)), + ) + .await?; + } + + objects.register_connection(conn.uuid(), &path); + Ok(()) + } + + /// Removes a connection from the tree + /// + /// * `uuid`: UUID of the connection to remove. + pub async fn remove_connection(&mut self, uuid: Uuid) -> Result<(), ServiceError> { + let mut objects = self.objects.lock(); + let Some(path) = objects.connection_path(uuid) else { + return Ok(()) + }; + self.remove_connection_on(path).await?; + objects.deregister_connection(uuid).unwrap(); + Ok(()) + } + + /// Adds connections to the D-Bus tree. + /// + /// * `connections`: list of connections. + async fn add_connections(&self, connections: &Vec) -> Result<(), ServiceError> { + for conn in connections.iter() { + self.add_connection(conn).await?; + } + + self.add_interface( + CONNECTIONS_PATH, + interfaces::Connections::new(Arc::clone(&self.objects), self.actions.clone()), + ) + .await?; + + Ok(()) + } + + /// Clears all the connections from the tree. + async fn remove_connections(&self) -> Result<(), ServiceError> { + let mut objects = self.objects.lock(); + for path in objects.connections.values() { + self.remove_connection_on(path.as_str()).await?; + } + objects.connections.clear(); + Ok(()) + } + + /// Clears all the devices from the tree. + async fn remove_devices(&mut self) -> Result<(), ServiceError> { + let object_server = self.connection.object_server(); + let mut objects = self.objects.lock(); + for path in objects.devices.values() { + object_server + .remove::(path.as_str()) + .await?; + } + objects.devices.clear(); + Ok(()) + } + + /// Removes a connection object on the given path + /// + /// * `path`: connection D-Bus path. + async fn remove_connection_on(&self, path: &str) -> Result<(), ServiceError> { + let object_server = self.connection.object_server(); + _ = object_server.remove::(path).await; + object_server.remove::(path).await?; + object_server + .remove::(path) + .await?; + Ok(()) + } + + async fn add_interface(&self, path: &str, iface: T) -> Result + where + T: zbus::Interface, + { + let object_server = self.connection.object_server(); + Ok(object_server.at(path.clone(), iface).await?) + } +} + +/// Objects paths for known devices and connections +/// +/// TODO: use zvariant::OwnedObjectPath instead of String as values. +#[derive(Debug, Default)] +pub struct ObjectsRegistry { + /// device_name (eth0) -> object_path + pub devices: HashMap, + /// uuid -> object_path + pub connections: HashMap, +} + +impl ObjectsRegistry { + /// Registers a network device. + /// + /// * `name`: device name. + /// * `path`: object path. + pub fn register_device(&mut self, name: &str, path: &str) { + self.devices.insert(name.to_string(), path.to_string()); + } + + /// Registers a network connection. + /// + /// * `uuid`: connection UUID. + /// * `path`: object path. + pub fn register_connection(&mut self, uuid: Uuid, path: &str) { + self.connections.insert(uuid, path.to_string()); + } + + /// Returns the path for a connection. + /// + /// * `uuid`: connection UUID. + pub fn connection_path(&self, uuid: Uuid) -> Option<&str> { + self.connections.get(&uuid).map(|p| p.as_str()) + } + + /// Deregisters a network connection. + /// + /// * `uuid`: connection UUID. + pub fn deregister_connection(&mut self, uuid: Uuid) -> Option { + self.connections.remove(&uuid) + } + + /// Returns all devices paths. + pub fn devices_paths(&self) -> Vec { + self.devices.values().map(|p| p.to_string()).collect() + } + + /// Returns all connection paths. + pub fn connections_paths(&self) -> Vec { + self.connections.values().map(|p| p.to_string()).collect() + } +} diff --git a/rust/agama-network/src/error.rs b/rust/agama-network/src/error.rs new file mode 100644 index 0000000000..baa173fba0 --- /dev/null +++ b/rust/agama-network/src/error.rs @@ -0,0 +1,33 @@ +//! Error types. +use std::net::AddrParseError; +use thiserror::Error; +use uuid::Uuid; + +/// Errors that are related to the network configuration. +#[derive(Error, Debug)] +pub enum NetworkStateError { + #[error("Invalid connection name: '{0}'")] + UnknownConnection(Uuid), + #[error("Invalid connection UUID: '{0}'")] + InvalidUuid(String), + #[error("Invalid IP address")] + InvalidIpv4Addr(#[from] AddrParseError), + #[error("Invalid IP method: '{0}'")] + InvalidIpMethod(u8), + #[error("Invalid wireless mode: '{0}'")] + InvalidWirelessMode(u8), + #[error("Connection '{0}' already exists")] + ConnectionExists(Uuid), + #[error("Invalid device type: '{0}'")] + InvalidDeviceType(u8), + #[error("Invalid security wireless protocol: '{0}'")] + InvalidSecurityProtocol(String), + #[error("Adapter error: '{0}'")] + AdapterError(String), +} + +impl From for zbus::fdo::Error { + fn from(value: NetworkStateError) -> zbus::fdo::Error { + zbus::fdo::Error::Failed(format!("Network error: {}", value.to_string())) + } +} diff --git a/rust/agama-network/src/lib.rs b/rust/agama-network/src/lib.rs new file mode 100644 index 0000000000..93e8dfdc8d --- /dev/null +++ b/rust/agama-network/src/lib.rs @@ -0,0 +1,53 @@ +//! Network configuration service for Agama +//! +//! This library implements the network configuration service for Agama. +//! +//! ## Connections and devices +//! +//! The library is built around the concepts of network devices and connections, akin to +//! NetworkManager approach. +//! +//! Each network device is exposed as a D-Bus object using a path like +//! `/org/opensuse/Agama/Network1/devices/[0-9]+`. At this point, those objects expose a bit of +//! information about network devices. The entry point for the devices is the +//! `/org/opensuse/Agama/Network1/devices` object, that expose a `GetDevices` method that returns +//! the paths for the devices objects. +//! +//! The network configuration is exposed through the connections objects as +//! `/org/opensuse/Agama/Network1/connections/[0-9]+`. Those objects are composed of several +//! D-Bus interfaces depending on its type: +//! +//! * `org.opensuse.Agama.Network1.Connection` exposes common information across all connection +//! types. +//! * `org.opensuse.Agama.Network1.Connection.IPv4` includes IPv4 settings, like the configuration method +//! (DHCP, manual, etc.), IP addresses, name servers and so on. +//! * `org.opensuse.Agama.Network1.Connection.Wireless` exposes the configuration for wireless +//! connections. +//! +//! Analogous to the devices API, there is a special `/org/opensuse/Agama/Network1/connections` +//! object that implements a few methods that are related to the collection of connections like +//! `GetConnections`, `AddConnection` and `RemoveConnection`. Additionally, it implements an +//! `Apply` method to write the changes to the NetworkManager service. +//! +//! ## Limitations +//! +//! We expect to address the following problems as we evolve the API, but it is noteworthy to have +//! them in mind: +//! +//! * The devices list does not reflect the changes in the system. For instance, it is not updated +//! when a device is connected to the system. +//! * Many configuration types are still missing (bridges, bonding, etc.). + +mod action; +mod adapter; +pub mod dbus; +pub mod error; +pub mod model; +mod nm; +pub mod system; + +pub use action::Action; +pub use adapter::Adapter; +pub use dbus::NetworkService; +pub use model::NetworkState; +pub use system::NetworkSystem; diff --git a/rust/agama-network/src/model.rs b/rust/agama-network/src/model.rs new file mode 100644 index 0000000000..1c124193ce --- /dev/null +++ b/rust/agama-network/src/model.rs @@ -0,0 +1,441 @@ +//! Representation of the network configuration +//! +//! * This module contains the types that represent the network concepts. They are supposed to be +//! agnostic from the real network service (e.g., NetworkManager). +use uuid::Uuid; + +use crate::error::NetworkStateError; +use std::{fmt, net::Ipv4Addr}; + +#[derive(Default)] +pub struct NetworkState { + pub devices: Vec, + pub connections: Vec, +} + +impl NetworkState { + /// Returns a NetworkState struct with the given devices and connections. + /// + /// * `devices`: devices to include in the state. + /// * `connections`: connections to include in the state. + pub fn new(devices: Vec, connections: Vec) -> Self { + Self { + devices, + connections, + } + } + + /// Get device by name + /// + /// * `name`: device name + pub fn get_device(&self, name: &str) -> Option<&Device> { + self.devices.iter().find(|d| d.name == name) + } + + /// Get connection by UUID + /// + /// * `uuid`: connection UUID + pub fn get_connection(&self, uuid: Uuid) -> Option<&Connection> { + self.connections.iter().find(|c| c.uuid() == uuid) + } + + /// Get connection by UUID as mutable + /// + /// * `uuid`: connection UUID + pub fn get_connection_mut(&mut self, uuid: Uuid) -> Option<&mut Connection> { + self.connections.iter_mut().find(|c| c.uuid() == uuid) + } + + /// Adds a new connection. + /// + /// It uses the `id` to decide whether the connection already exists. + pub fn add_connection(&mut self, conn: Connection) -> Result<(), NetworkStateError> { + if let Some(_) = self.get_connection(conn.uuid()) { + return Err(NetworkStateError::ConnectionExists(conn.uuid())); + } + + self.connections.push(conn); + Ok(()) + } + + /// Updates a connection with a new one. + /// + /// It uses the `id` to decide which connection to update. + /// + /// Additionally, it registers the connection to be removed when the changes are applied. + pub fn update_connection(&mut self, conn: Connection) -> Result<(), NetworkStateError> { + let Some(old_conn) = self.get_connection_mut(conn.uuid()) else { + return Err(NetworkStateError::UnknownConnection(conn.uuid())); + }; + + *old_conn = conn; + Ok(()) + } + + /// Removes a connection from the state. + /// + /// Additionally, it registers the connection to be removed when the changes are applied. + pub fn remove_connection(&mut self, uuid: Uuid) -> Result<(), NetworkStateError> { + let Some(conn) = self.get_connection_mut(uuid) else { + return Err(NetworkStateError::UnknownConnection(uuid)); + }; + + conn.remove(); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use uuid::Uuid; + + use super::{BaseConnection, Connection, EthernetConnection, NetworkState}; + use crate::error::NetworkStateError; + + #[test] + fn test_add_connection() { + let mut state = NetworkState::default(); + let uuid = Uuid::new_v4(); + let base = BaseConnection { + uuid, + ..Default::default() + }; + let conn0 = Connection::Ethernet(EthernetConnection { base }); + state.add_connection(conn0).unwrap(); + let found = state.get_connection(uuid).unwrap(); + assert_eq!(found.uuid(), uuid); + } + + #[test] + fn test_add_duplicated_connection() { + let mut state = NetworkState::default(); + let uuid = Uuid::new_v4(); + let base = BaseConnection { + uuid, + ..Default::default() + }; + let conn0 = Connection::Ethernet(EthernetConnection { base }); + state.add_connection(conn0.clone()).unwrap(); + let error = state.add_connection(conn0).unwrap_err(); + assert!(matches!(error, NetworkStateError::ConnectionExists(_))); + } + + #[test] + fn test_update_connection() { + let mut state = NetworkState::default(); + let uuid = Uuid::new_v4(); + let base0 = BaseConnection { + uuid, + ..Default::default() + }; + let conn0 = Connection::Ethernet(EthernetConnection { base: base0 }); + state.add_connection(conn0).unwrap(); + + let base1 = BaseConnection { + uuid, + ..Default::default() + }; + let conn2 = Connection::Ethernet(EthernetConnection { base: base1 }); + state.update_connection(conn2).unwrap(); + let found = state.get_connection(uuid).unwrap(); + assert_eq!(found.uuid(), uuid); + } + + #[test] + fn test_update_unknown_connection() { + let mut state = NetworkState::default(); + let uuid = Uuid::new_v4(); + let base = BaseConnection { + uuid, + ..Default::default() + }; + let conn0 = Connection::Ethernet(EthernetConnection { base }); + let error = state.update_connection(conn0).unwrap_err(); + assert!(matches!(error, NetworkStateError::UnknownConnection(_))); + } + + #[test] + fn test_remove_connection() { + let mut state = NetworkState::default(); + let uuid = Uuid::new_v4(); + let base0 = BaseConnection { + uuid, + ..Default::default() + }; + let conn0 = Connection::Ethernet(EthernetConnection { base: base0 }); + state.add_connection(conn0).unwrap(); + state.remove_connection(uuid).unwrap(); + let found = state.get_connection(uuid).unwrap(); + assert!(found.is_removed()); + } + + #[test] + fn test_remove_unknown_connection() { + let mut state = NetworkState::default(); + let error = state.remove_connection(Uuid::new_v4()).unwrap_err(); + assert!(matches!(error, NetworkStateError::UnknownConnection(_))); + } +} + +/// Network device +#[derive(Debug, Clone)] +pub struct Device { + pub name: String, + pub type_: DeviceType, +} + +#[derive(Debug, PartialEq, Copy, Clone)] +pub enum DeviceType { + Loopback = 0, + Ethernet = 1, + Wireless = 2, +} + +impl TryFrom for DeviceType { + type Error = NetworkStateError; + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(DeviceType::Loopback), + 1 => Ok(DeviceType::Ethernet), + 2 => Ok(DeviceType::Wireless), + _ => Err(NetworkStateError::InvalidDeviceType(value)), + } + } +} + +/// Represents an available network connection +#[derive(Debug, PartialEq, Clone)] +pub enum Connection { + Ethernet(EthernetConnection), + Wireless(WirelessConnection), + Loopback(LoopbackConnection), +} + +impl Connection { + pub fn new(id: String, device_type: DeviceType) -> Self { + let base = BaseConnection { + id: id.to_string(), + ..Default::default() + }; + match device_type { + DeviceType::Wireless => Connection::Wireless(WirelessConnection { + base, + ..Default::default() + }), + DeviceType::Loopback => Connection::Loopback(LoopbackConnection { base }), + DeviceType::Ethernet => Connection::Ethernet(EthernetConnection { base }), + } + } + + /// TODO: implement a macro to reduce the amount of repetitive code. The same applies to + /// the base_mut function. + pub fn base(&self) -> &BaseConnection { + match &self { + Connection::Ethernet(conn) => &conn.base, + Connection::Wireless(conn) => &conn.base, + Connection::Loopback(conn) => &conn.base, + } + } + + pub fn base_mut(&mut self) -> &mut BaseConnection { + match self { + Connection::Ethernet(conn) => &mut conn.base, + Connection::Wireless(conn) => &mut conn.base, + Connection::Loopback(conn) => &mut conn.base, + } + } + + pub fn id(&self) -> &str { + self.base().id.as_str() + } + + pub fn uuid(&self) -> Uuid { + self.base().uuid + } + + pub fn ipv4(&self) -> &Ipv4Config { + &self.base().ipv4 + } + + pub fn ipv4_mut(&mut self) -> &mut Ipv4Config { + &mut self.base_mut().ipv4 + } + + pub fn remove(&mut self) { + self.base_mut().status = Status::Removed; + } + + pub fn is_removed(&self) -> bool { + self.base().status == Status::Removed + } +} + +#[derive(Debug, Default, Clone)] +pub struct BaseConnection { + pub id: String, + pub uuid: Uuid, + pub ipv4: Ipv4Config, + pub status: Status, +} + +impl PartialEq for BaseConnection { + fn eq(&self, other: &Self) -> bool { + self.id == other.id && self.uuid == other.uuid && self.ipv4 == other.ipv4 + } +} + +#[derive(Debug, Default, Clone, Copy, PartialEq)] +pub enum Status { + #[default] + Present, + Removed, +} + +#[derive(Debug, Default, PartialEq, Clone)] +pub struct Ipv4Config { + pub method: IpMethod, + pub addresses: Vec<(Ipv4Addr, u32)>, + pub nameservers: Vec, + pub gateway: Option, +} + +#[derive(Debug, Default, Copy, Clone, PartialEq)] +pub enum IpMethod { + #[default] + Disabled = 0, + Auto = 1, + Manual = 2, + LinkLocal = 3, +} +impl fmt::Display for IpMethod { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let name = match &self { + IpMethod::Disabled => "disabled", + IpMethod::Auto => "auto", + IpMethod::Manual => "manual", + IpMethod::LinkLocal => "link-local", + }; + write!(f, "{}", name) + } +} + +// NOTE: we could use num-derive. +impl TryFrom for IpMethod { + type Error = NetworkStateError; + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(IpMethod::Disabled), + 1 => Ok(IpMethod::Auto), + 2 => Ok(IpMethod::Manual), + 3 => Ok(IpMethod::LinkLocal), + _ => Err(NetworkStateError::InvalidIpMethod(value)), + } + } +} + +#[derive(Debug, Default, PartialEq, Clone)] +pub struct EthernetConnection { + pub base: BaseConnection, +} + +#[derive(Debug, Default, PartialEq, Clone)] +pub struct WirelessConnection { + pub base: BaseConnection, + pub wireless: WirelessConfig, +} + +#[derive(Debug, Default, PartialEq, Clone)] +pub struct LoopbackConnection { + pub base: BaseConnection, +} + +#[derive(Debug, Default, PartialEq, Clone)] +pub struct WirelessConfig { + pub mode: WirelessMode, + pub ssid: Vec, + pub password: Option, + pub security: SecurityProtocol, +} + +#[derive(Debug, Default, Clone, Copy, PartialEq)] +pub enum WirelessMode { + #[default] + Infra = 0, + AdHoc = 1, + Mesh = 2, + AP = 3, +} + +impl TryFrom for WirelessMode { + type Error = NetworkStateError; + + fn try_from(value: u8) -> Result { + match value { + 1 => Ok(WirelessMode::AdHoc), + 2 => Ok(WirelessMode::Infra), + 3 => Ok(WirelessMode::AP), + 4 => Ok(WirelessMode::Mesh), + _ => Err(NetworkStateError::InvalidWirelessMode(value)), + } + } +} + +impl fmt::Display for WirelessMode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let name = match &self { + WirelessMode::AdHoc => "adhoc", + WirelessMode::Infra => "infrastructure", + WirelessMode::AP => "ap", + WirelessMode::Mesh => "mesh", + }; + write!(f, "{}", name) + } +} + +#[derive(Debug, Clone, Copy, Default, PartialEq)] +pub enum SecurityProtocol { + #[default] + WEP, // No encryption or WEP ("none") + OWE, // Opportunistic Wireless Encryption ("owe") + DynamicWEP, // Dynamic WEP ("ieee8021x") + WPA2, // WPA2 + WPA3 personal ("wpa-psk") + WPA3Personal, // WPA3 personal only ("sae") + WPA2Enterprise, // WPA2 + WPA3 Enterprise ("wpa-eap") + WPA3Only, // WPA3 only ("wpa-eap-suite-b192") +} + +impl fmt::Display for SecurityProtocol { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let value = match &self { + SecurityProtocol::WEP => "none", + SecurityProtocol::OWE => "owe", + SecurityProtocol::DynamicWEP => "ieee8021x", + SecurityProtocol::WPA2 => "wpa-psk", + SecurityProtocol::WPA3Personal => "sae", + SecurityProtocol::WPA2Enterprise => "wpa-eap", + SecurityProtocol::WPA3Only => "wpa-eap-suite-b192", + }; + write!(f, "{}", value) + } +} + +impl TryFrom<&str> for SecurityProtocol { + type Error = NetworkStateError; + + fn try_from(value: &str) -> Result { + match value { + "none" => Ok(SecurityProtocol::WEP), + "owe" => Ok(SecurityProtocol::OWE), + "ieee8021x" => Ok(SecurityProtocol::DynamicWEP), + "wpa-psk" => Ok(SecurityProtocol::WPA2), + "sae" => Ok(SecurityProtocol::WPA3Personal), + "wpa-eap" => Ok(SecurityProtocol::WPA2Enterprise), + "wpa-eap-suite-b192" => Ok(SecurityProtocol::WPA3Only), + _ => Err(NetworkStateError::InvalidSecurityProtocol( + value.to_string(), + )), + } + } +} diff --git a/rust/agama-network/src/nm.rs b/rust/agama-network/src/nm.rs new file mode 100644 index 0000000000..e841e0f407 --- /dev/null +++ b/rust/agama-network/src/nm.rs @@ -0,0 +1,15 @@ +//! Support for interacting with [NetworkManager](https://networkmanager.dev/). +//! +//! This module defines [a NetworkManager client](client::NetworkManagerClient) and a set of +//! structs and enums to work with NetworkManager configuration. It is intended to be used +//! internally, so the API is focused on Agama's use cases. + +mod adapter; +mod client; +mod dbus; +mod error; +mod model; +mod proxies; + +pub use adapter::NetworkManagerAdapter; +pub use client::NetworkManagerClient; diff --git a/rust/agama-network/src/nm/adapter.rs b/rust/agama-network/src/nm/adapter.rs new file mode 100644 index 0000000000..508442e6aa --- /dev/null +++ b/rust/agama-network/src/nm/adapter.rs @@ -0,0 +1,63 @@ +use crate::{ + model::{Connection, NetworkState}, + nm::NetworkManagerClient, + Adapter, +}; +use agama_lib::error::ServiceError; + +/// An adapter for NetworkManager +pub struct NetworkManagerAdapter<'a> { + client: NetworkManagerClient<'a>, +} + +impl<'a> NetworkManagerAdapter<'a> { + /// Returns the adapter for system's NetworkManager. + pub async fn from_system() -> Result, ServiceError> { + let client = NetworkManagerClient::from_system().await?; + Ok(Self { client }) + } + + /// Determines whether the write operation is supported for a connection + /// + /// * `conn`: connection + fn is_writable(conn: &Connection) -> bool { + match conn { + Connection::Loopback(_) => false, + _ => true, + } + } +} + +impl<'a> Adapter for NetworkManagerAdapter<'a> { + fn read(&self) -> Result> { + async_std::task::block_on(async { + let devices = self.client.devices().await?; + let connections = self.client.connections().await?; + + Ok(NetworkState::new(devices, connections)) + }) + } + + fn write(&self, network: &NetworkState) -> Result<(), Box> { + // By now, traits do not support async functions. Using `task::block_on` allows + // to use 'await'. + async_std::task::block_on(async { + for conn in &network.connections { + if !Self::is_writable(conn) { + continue; + } + if conn.is_removed() { + if let Err(e) = self.client.remove_connection(conn.uuid()).await { + eprintln!("Could not remove the connection {}: {}", conn.uuid(), e); + } + } else { + if let Err(e) = self.client.add_or_update_connection(conn).await { + eprintln!("Could not add/update the connection {}: {}", conn.uuid(), e); + } + } + } + }); + // FIXME: indicate which connections could not be written. + Ok(()) + } +} diff --git a/rust/agama-network/src/nm/client.rs b/rust/agama-network/src/nm/client.rs new file mode 100644 index 0000000000..cb366e167e --- /dev/null +++ b/rust/agama-network/src/nm/client.rs @@ -0,0 +1,118 @@ +//! NetworkManager client. +use super::dbus::{connection_from_dbus, connection_to_dbus, merge_dbus_connections}; +use super::model::NmDeviceType; +use super::proxies::{ConnectionProxy, DeviceProxy, NetworkManagerProxy, SettingsProxy}; +use crate::model::{Connection, Device}; +use agama_lib::error::ServiceError; +use uuid::Uuid; +use zbus; + +/// Simplified NetworkManager D-Bus client. +/// +/// Implements a minimal API to be used internally. At this point, it allows to query the list of +/// network devices and connections, converting them to its own data types. +pub struct NetworkManagerClient<'a> { + connection: zbus::Connection, + nm_proxy: NetworkManagerProxy<'a>, +} + +impl<'a> NetworkManagerClient<'a> { + /// Creates a NetworkManagerClient connecting to the system bus. + pub async fn from_system() -> Result, ServiceError> { + let connection = zbus::Connection::system().await?; + Self::new(connection).await + } + + /// Creates a NetworkManagerClient using the given D-Bus connection. + /// + /// * `connection`: D-Bus connection. + pub async fn new( + connection: zbus::Connection, + ) -> Result, ServiceError> { + Ok(Self { + nm_proxy: NetworkManagerProxy::new(&connection).await?, + connection, + }) + } + + /// Returns the list of network devices. + pub async fn devices(&self) -> Result, ServiceError> { + let mut devs = vec![]; + for path in &self.nm_proxy.get_devices().await? { + let proxy = DeviceProxy::builder(&self.connection) + .path(path.as_str())? + .build() + .await?; + + let device_name = proxy.interface().await?; + let device_type = NmDeviceType(proxy.device_type().await?); + if let Ok(device_type) = device_type.try_into() { + devs.push(Device { + name: device_name, + type_: device_type, + }); + } else { + // TODO: use a logger + eprintln!( + "Unsupported device type {:?} for {}", + &device_type, &device_name + ); + } + } + + Ok(devs) + } + + /// Returns the list of network connections. + pub async fn connections(&self) -> Result, ServiceError> { + let proxy = SettingsProxy::new(&self.connection).await?; + let paths = proxy.list_connections().await?; + let mut connections: Vec = Vec::with_capacity(paths.len()); + for path in paths { + let proxy = ConnectionProxy::builder(&self.connection) + .path(path.as_str())? + .build() + .await?; + let settings = proxy.get_settings().await?; + // TODO: log an error if a connection is not found + if let Some(connection) = connection_from_dbus(settings) { + connections.push(connection.into()); + } + } + Ok(connections) + } + + /// Adds or updates a connection if it already exists. + /// + /// * `conn`: connection to add or update. + pub async fn add_or_update_connection(&self, conn: &Connection) -> Result<(), ServiceError> { + let new_conn = connection_to_dbus(conn); + if let Ok(proxy) = self.get_connection_proxy(conn.uuid()).await { + let original = proxy.get_settings().await?; + let merged = merge_dbus_connections(&original, &new_conn); + proxy.update(merged).await?; + } else { + let proxy = SettingsProxy::new(&self.connection).await?; + proxy.add_connection(new_conn).await?; + } + Ok(()) + } + + /// Removes a network connection. + pub async fn remove_connection(&self, uuid: Uuid) -> Result<(), ServiceError> { + let proxy = self.get_connection_proxy(uuid).await?; + proxy.delete().await?; + Ok(()) + } + + async fn get_connection_proxy(&self, uuid: Uuid) -> Result { + let proxy = SettingsProxy::new(&self.connection).await?; + let uuid_s = uuid.to_string(); + let path = proxy.get_connection_by_uuid(uuid_s.as_str()).await?; + let proxy = ConnectionProxy::builder(&self.connection) + .path(path)? + .build() + .await?; + Ok(proxy) + } +} diff --git a/rust/agama-network/src/nm/dbus.rs b/rust/agama-network/src/nm/dbus.rs new file mode 100644 index 0000000000..79a3fbdf50 --- /dev/null +++ b/rust/agama-network/src/nm/dbus.rs @@ -0,0 +1,464 @@ +//! This module implements some functions to convert from/to D-Bus types +//! +//! Working with hash maps coming from D-Bus is rather tedious and it is even worse when working +//! with nested hash maps (see [NestedHash] and [OwnedNestedHash]). +use super::model::*; +use crate::model::*; +use agama_lib::dbus::{NestedHash, OwnedNestedHash}; +use std::collections::HashMap; +use std::net::Ipv4Addr; +use uuid::Uuid; +use zbus::zvariant::{self, Value}; + +const ETHERNET_KEY: &str = "802-3-ethernet"; +const WIRELESS_KEY: &str = "802-11-wireless"; +const WIRELESS_SECURITY_KEY: &str = "802-11-wireless-security"; +const LOOPBACK_KEY: &str = "loopback"; + +/// Converts a connection struct into a HashMap that can be sent over D-Bus. +/// +/// * `conn`: Connection to cnvert. +pub fn connection_to_dbus(conn: &Connection) -> NestedHash { + let mut result = NestedHash::new(); + let mut connection_dbus = + HashMap::from([("id", conn.id().into()), ("type", ETHERNET_KEY.into())]); + result.insert("ipv4", ipv4_to_dbus(conn.ipv4())); + + if let Connection::Wireless(wireless) = conn { + connection_dbus.insert("type", "802-11-wireless".into()); + let wireless_dbus = wireless_config_to_dbus(wireless); + for (k, v) in wireless_dbus { + result.insert(k, v); + } + } + result.insert("connection", connection_dbus); + result +} + +/// Converts an OwnedNestedHash from D-Bus into a Connection. +/// +/// This functions tries to turn a OwnedHashMap coming from D-Bus into a Connection. +pub fn connection_from_dbus(conn: OwnedNestedHash) -> Option { + let base = base_connection_from_dbus(&conn)?; + + if let Some(wireless_config) = wireless_config_from_dbus(&conn) { + return Some(Connection::Wireless(WirelessConnection { + base, + wireless: wireless_config, + })); + } + + if conn.get(LOOPBACK_KEY).is_some() { + return Some(Connection::Loopback(LoopbackConnection { base })); + }; + + if conn.get(ETHERNET_KEY).is_some() { + return Some(Connection::Ethernet(EthernetConnection { base })); + }; + + None +} + +/// Merges a NestedHash and an OwnedNestedHash connections. +/// +/// Only the top-level sections that are present in the `original` hash are considered for update. +/// +/// * `original`: original hash coming from D-Bus. +/// * `updated`: updated hash to write to D-Bus. +pub fn merge_dbus_connections<'a>( + original: &'a OwnedNestedHash, + updated: &'a NestedHash, +) -> NestedHash<'a> { + let mut merged = HashMap::with_capacity(original.len()); + for (key, orig_section) in original { + let mut inner: HashMap<&str, zbus::zvariant::Value> = + HashMap::with_capacity(orig_section.len()); + for (inner_key, value) in orig_section { + inner.insert(inner_key.as_str(), value.into()); + } + if let Some(upd_section) = updated.get(key.as_str()) { + for (inner_key, value) in upd_section { + inner.insert(inner_key, value.clone()); + } + } + merged.insert(key.as_str(), inner.into()); + } + cleanup_dbus_connection(&mut merged); + merged +} + +/// Cleans up the NestedHash that represents a connection. +/// +/// By now it just removes the "addresses" key from the "ipv4" and "ipv6" objects, which is +/// replaced with "address-data". However, if "addresses" is present, it takes precedence. +/// +/// * `conn`: connection represented as a NestedHash. +fn cleanup_dbus_connection<'a>(conn: &'a mut NestedHash) { + if let Some(ipv4) = conn.get_mut("ipv4") { + ipv4.remove("addresses"); + } + + if let Some(ipv6) = conn.get_mut("ipv6") { + ipv6.remove("addresses"); + } +} + +fn ipv4_to_dbus(ipv4: &Ipv4Config) -> HashMap<&str, zvariant::Value> { + let addresses: Vec> = ipv4 + .addresses + .iter() + .map(|(addr, prefix)| { + HashMap::from([ + ("address", Value::new(addr.to_string())), + ("prefix", Value::new(prefix)), + ]) + }) + .collect(); + let address_data: Value = addresses.into(); + + let dns_data: Value = ipv4 + .nameservers + .iter() + .map(|ns| ns.to_string().into()) + .collect::>() + .into(); + + let mut ipv4_dbus = HashMap::from([ + ("address-data", address_data), + ("dns-data", dns_data), + ("method", ipv4.method.to_string().into()), + ]); + + if let Some(gateway) = ipv4.gateway { + ipv4_dbus.insert("gateway", gateway.to_string().into()); + } + ipv4_dbus +} + +fn wireless_config_to_dbus(conn: &WirelessConnection) -> NestedHash { + let config = &conn.wireless; + let wireless: HashMap<&str, zvariant::Value> = HashMap::from([ + ("mode", Value::new(config.mode.to_string())), + ("ssid", Value::new(&config.ssid)), + ]); + + let mut security: HashMap<&str, zvariant::Value> = + HashMap::from([("key-mgmt", config.security.to_string().into())]); + + if let Some(password) = &config.password { + security.insert("psk", password.to_string().into()); + } + + NestedHash::from([ + ("802-11-wireless", wireless), + ("802-11-wireless-security", security), + ]) +} + +fn base_connection_from_dbus(conn: &OwnedNestedHash) -> Option { + let Some(connection) = conn.get("connection") else { + return None; + }; + + let id: &str = connection.get("id")?.downcast_ref()?; + let uuid: &str = connection.get("uuid")?.downcast_ref()?; + let uuid: Uuid = uuid.try_into().ok()?; + let mut base_connection = BaseConnection { + id: id.to_string(), + uuid, + ..Default::default() + }; + + if let Some(ipv4) = conn.get("ipv4") { + base_connection.ipv4 = ipv4_config_from_dbus(ipv4)?; + } + + Some(base_connection) +} + +fn ipv4_config_from_dbus(ipv4: &HashMap) -> Option { + let method: &str = ipv4.get("method")?.downcast_ref()?; + let address_data = ipv4.get("address-data")?; + let address_data = address_data.downcast_ref::()?; + let mut addresses: Vec<(Ipv4Addr, u32)> = vec![]; + for addr in address_data.get() { + let dict = addr.downcast_ref::()?; + let map = >>::try_from(dict.clone()).unwrap(); + let addr_str: &str = map.get("address")?.downcast_ref()?; + let prefix: &u32 = map.get("prefix")?.downcast_ref()?; + addresses.push((addr_str.parse().unwrap(), *prefix)) + } + let mut ipv4_config = Ipv4Config { + method: NmMethod(method.to_string()).try_into().ok()?, + addresses, + ..Default::default() + }; + + if let Some(dns_data) = ipv4.get("dns-data") { + let dns_data = dns_data.downcast_ref::()?; + for server in dns_data.get() { + let server: &str = server.downcast_ref()?; + ipv4_config.nameservers.push(server.parse().unwrap()); + } + } + + if let Some(gateway) = ipv4.get("gateway") { + let gateway: &str = gateway.downcast_ref()?; + ipv4_config.gateway = Some(gateway.parse().unwrap()); + } + + Some(ipv4_config) +} + +fn wireless_config_from_dbus(conn: &OwnedNestedHash) -> Option { + let Some(wireless) = conn.get(WIRELESS_KEY) else { + return None; + }; + + let mode: &str = wireless.get("mode")?.downcast_ref()?; + let ssid = wireless.get("ssid")?; + let ssid: &zvariant::Array = ssid.downcast_ref()?; + let ssid: Vec = ssid + .get() + .iter() + .map(|u| *u.downcast_ref::().unwrap()) + .collect(); + let mut wireless_config = WirelessConfig { + mode: NmWirelessMode(mode.to_string()).try_into().ok()?, + ssid, + ..Default::default() + }; + + if let Some(security) = conn.get(WIRELESS_SECURITY_KEY) { + let key_mgmt: &str = security.get("key-mgmt")?.downcast_ref()?; + wireless_config.security = NmKeyManagement(key_mgmt.to_string()).try_into().ok()?; + } + + Some(wireless_config) +} + +#[cfg(test)] +mod test { + use super::{ + connection_from_dbus, connection_to_dbus, merge_dbus_connections, NestedHash, + OwnedNestedHash, + }; + use crate::{model::*, nm::dbus::ETHERNET_KEY}; + use std::{collections::HashMap, net::Ipv4Addr}; + use uuid::Uuid; + use zbus::zvariant::{self, OwnedValue, Value}; + + #[test] + fn test_connection_from_dbus() { + let uuid = Uuid::new_v4().to_string(); + let connection_section = HashMap::from([ + ("id".to_string(), Value::new("eth0").to_owned()), + ("uuid".to_string(), Value::new(uuid).to_owned()), + ]); + + let address_data = vec![HashMap::from([ + ("address".to_string(), Value::new("192.168.0.10")), + ("prefix".to_string(), Value::new(24 as u32)), + ])]; + + let ipv4_section = HashMap::from([ + ("method".to_string(), Value::new("auto").to_owned()), + ( + "address-data".to_string(), + Value::new(address_data).to_owned(), + ), + ("gateway".to_string(), Value::new("192.168.0.1").to_owned()), + ( + "dns-data".to_string(), + Value::new(vec!["192.168.0.2"]).to_owned(), + ), + ]); + + let dbus_conn = HashMap::from([ + ("connection".to_string(), connection_section), + ("ipv4".to_string(), ipv4_section), + (ETHERNET_KEY.to_string(), build_ethernet_section_from_dbus()), + ]); + + let connection = connection_from_dbus(dbus_conn).unwrap(); + + assert_eq!(connection.id(), "eth0"); + let ipv4 = connection.ipv4(); + assert_eq!(ipv4.addresses, vec![(Ipv4Addr::new(192, 168, 0, 10), 24)]); + assert_eq!(ipv4.nameservers, vec![Ipv4Addr::new(192, 168, 0, 2)]); + assert_eq!(ipv4.method, IpMethod::Auto); + } + + #[test] + fn test_connection_from_dbus_missing_connection() { + let dbus_conn: HashMap> = HashMap::new(); + let connection = connection_from_dbus(dbus_conn); + assert_eq!(connection, None); + } + + #[test] + fn test_connection_from_dbus_wireless() { + let uuid = Uuid::new_v4().to_string(); + let connection_section = HashMap::from([ + ("id".to_string(), Value::new("wlan0").to_owned()), + ("uuid".to_string(), Value::new(uuid).to_owned()), + ]); + + let wireless_section = HashMap::from([ + ("mode".to_string(), Value::new("infrastructure").to_owned()), + ( + "ssid".to_string(), + Value::new("agama".as_bytes()).to_owned(), + ), + ]); + + let security_section = + HashMap::from([("key-mgmt".to_string(), Value::new("wpa-psk").to_owned())]); + + let dbus_conn = HashMap::from([ + ("connection".to_string(), connection_section), + ("802-11-wireless".to_string(), wireless_section), + ("802-11-wireless-security".to_string(), security_section), + ]); + + let connection = connection_from_dbus(dbus_conn).unwrap(); + assert!(matches!(connection, Connection::Wireless(_))); + if let Connection::Wireless(connection) = connection { + assert_eq!(connection.wireless.ssid, vec![97, 103, 97, 109, 97]); + assert_eq!(connection.wireless.mode, WirelessMode::Infra); + assert_eq!(connection.wireless.security, SecurityProtocol::WPA2) + } + } + + #[test] + fn test_dbus_from_wireless_connection() { + let config = WirelessConfig { + mode: WirelessMode::Infra, + security: SecurityProtocol::WPA2, + ssid: "agama".as_bytes().into(), + ..Default::default() + }; + let wireless = WirelessConnection { + base: build_base_connection(), + wireless: config, + ..Default::default() + }; + let wireless = Connection::Wireless(wireless); + let wireless_dbus = connection_to_dbus(&wireless); + + let wireless = wireless_dbus.get("802-11-wireless").unwrap(); + let mode: &str = wireless.get("mode").unwrap().downcast_ref().unwrap(); + assert_eq!(mode, "infrastructure"); + + let ssid: &zvariant::Array = wireless.get("ssid").unwrap().downcast_ref().unwrap(); + let ssid: Vec = ssid + .get() + .iter() + .map(|u| *u.downcast_ref::().unwrap()) + .collect(); + assert_eq!(ssid, "agama".as_bytes()); + + let security = wireless_dbus.get("802-11-wireless-security").unwrap(); + let key_mgmt: &str = security.get("key-mgmt").unwrap().downcast_ref().unwrap(); + assert_eq!(key_mgmt, "wpa-psk"); + } + + #[test] + fn test_dbus_from_ethernet_connection() { + let ethernet = build_ethernet_connection(); + let ethernet_dbus = connection_to_dbus(ðernet); + check_dbus_base_connection(ðernet_dbus); + } + + #[test] + fn test_merge_dbus_connections() { + let mut original = OwnedNestedHash::new(); + let connection = HashMap::from([ + ("id".to_string(), Value::new("conn0".to_string()).to_owned()), + ( + "type".to_string(), + Value::new(ETHERNET_KEY.to_string()).to_owned(), + ), + ]); + let ipv4 = HashMap::from([ + ( + "method".to_string(), + Value::new("manual".to_string()).to_owned(), + ), + ( + "gateway".to_string(), + Value::new("192.168.1.1".to_string()).to_owned(), + ), + ( + "addresses".to_string(), + Value::new(vec!["192.168.1.1"]).to_owned(), + ), + ]); + original.insert("connection".to_string(), connection); + original.insert("ipv4".to_string(), ipv4); + + let base = BaseConnection { + id: "agama".to_string(), + ..Default::default() + }; + let ethernet = EthernetConnection { + base, + ..Default::default() + }; + let updated = Connection::Ethernet(ethernet); + let updated = connection_to_dbus(&updated); + + let merged = merge_dbus_connections(&original, &updated); + let connection = merged.get("connection").unwrap(); + assert_eq!( + *connection.get("id").unwrap(), + Value::new("agama".to_string()) + ); + + let ipv4 = merged.get("ipv4").unwrap(); + assert_eq!( + *ipv4.get("method").unwrap(), + Value::new("disabled".to_string()) + ); + assert_eq!( + *ipv4.get("gateway").unwrap(), + Value::new("192.168.1.1".to_string()) + ); + assert!(ipv4.get("addresses").is_none()); + } + + fn build_ethernet_section_from_dbus() -> HashMap { + HashMap::from([("auto-negotiate".to_string(), true.into())]) + } + + fn build_base_connection() -> BaseConnection { + let addresses = vec![(Ipv4Addr::new(192, 168, 0, 2), 24)]; + let ipv4 = Ipv4Config { + addresses, + gateway: Some(Ipv4Addr::new(192, 168, 0, 1)), + ..Default::default() + }; + BaseConnection { + id: "agama".to_string(), + ipv4, + ..Default::default() + } + } + + fn build_ethernet_connection() -> Connection { + let ethernet = EthernetConnection { + base: build_base_connection(), + }; + Connection::Ethernet(ethernet) + } + + fn check_dbus_base_connection(conn_dbus: &NestedHash) { + let connection_dbus = conn_dbus.get("connection").unwrap(); + let id: &str = connection_dbus.get("id").unwrap().downcast_ref().unwrap(); + assert_eq!(id, "agama"); + + let ipv4_dbus = conn_dbus.get("ipv4").unwrap(); + let gateway: &str = ipv4_dbus.get("gateway").unwrap().downcast_ref().unwrap(); + assert_eq!(gateway, "192.168.0.1"); + } +} diff --git a/rust/agama-network/src/nm/error.rs b/rust/agama-network/src/nm/error.rs new file mode 100644 index 0000000000..0d9627ee91 --- /dev/null +++ b/rust/agama-network/src/nm/error.rs @@ -0,0 +1,21 @@ +//! NetworkManager error types +use crate::error::NetworkStateError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum NmError { + #[error("Unsupported IP method: '{0}'")] + UnsupportedIpMethod(String), + #[error("Unsupported device type: '{0}'")] + UnsupportedDeviceType(u32), + #[error("Unsupported security protocol: '{0}'")] + UnsupportedSecurityProtocol(String), + #[error("Unsupported wireless mode: '{0}'")] + UnsupporedWirelessMode(String), +} + +impl From for NetworkStateError { + fn from(value: NmError) -> NetworkStateError { + NetworkStateError::AdapterError(value.to_string()) + } +} diff --git a/rust/agama-network/src/nm/model.rs b/rust/agama-network/src/nm/model.rs new file mode 100644 index 0000000000..0b6fb659dd --- /dev/null +++ b/rust/agama-network/src/nm/model.rs @@ -0,0 +1,178 @@ +//! Set of structs and enums to handle devices and connections from NetworkManager. +//! +//! This are meant to be used internally, so we omit everything it is not useful for us. + +/// NetworkManager wireless mode +/// +/// Using the newtype pattern around an String is enough. For proper support, we might replace this +/// struct with an enum. +use crate::{ + model::{DeviceType, IpMethod, SecurityProtocol, WirelessMode}, + nm::error::NmError, +}; +use std::fmt; + +#[derive(Debug, PartialEq)] +pub struct NmWirelessMode(pub String); + +impl Default for NmWirelessMode { + fn default() -> Self { + NmWirelessMode("infrastructure".to_string()) + } +} + +impl From<&str> for NmWirelessMode { + fn from(value: &str) -> Self { + Self(value.to_string()) + } +} + +impl NmWirelessMode { + pub fn as_str(&self) -> &str { + &self.0.as_str() + } +} + +impl TryFrom for WirelessMode { + type Error = NmError; + + fn try_from(value: NmWirelessMode) -> Result { + match value.as_str() { + "infrastructure" => Ok(WirelessMode::Infra), + "adhoc" => Ok(WirelessMode::AdHoc), + "mesh" => Ok(WirelessMode::Mesh), + "ap" => Ok(WirelessMode::AP), + _ => Err(NmError::UnsupporedWirelessMode(value.to_string())), + } + } +} + +impl fmt::Display for NmWirelessMode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", &self.0) + } +} + +/// Device types +/// +/// As we are using the number just to filter wireless devices, using the newtype +/// pattern around an u32 is enough. For proper support, we might replace this +/// struct with an enum. +#[derive(Debug, Clone, Copy)] +pub struct NmDeviceType(pub u32); + +impl From for u32 { + fn from(value: NmDeviceType) -> u32 { + value.0 + } +} + +impl Default for NmDeviceType { + fn default() -> Self { + NmDeviceType(0) + } +} + +impl TryFrom for DeviceType { + type Error = NmError; + + fn try_from(value: NmDeviceType) -> Result { + match value { + NmDeviceType(0) => Ok(DeviceType::Loopback), + NmDeviceType(1) => Ok(DeviceType::Ethernet), + NmDeviceType(2) => Ok(DeviceType::Wireless), + NmDeviceType(_) => Err(NmError::UnsupportedDeviceType(value.into())), + } + } +} + +/// Key management +/// +/// Using the newtype pattern around an String is enough. For proper support, we might replace this +/// struct with an enum. +#[derive(Debug, PartialEq)] +pub struct NmKeyManagement(pub String); + +impl Default for NmKeyManagement { + fn default() -> Self { + NmKeyManagement("none".to_string()) + } +} + +impl From<&str> for NmKeyManagement { + fn from(value: &str) -> Self { + Self(value.to_string()) + } +} + +impl TryFrom for SecurityProtocol { + type Error = NmError; + + fn try_from(value: NmKeyManagement) -> Result { + match value.as_str() { + "owe" => Ok(SecurityProtocol::OWE), + "ieee8021x" => Ok(SecurityProtocol::DynamicWEP), + "wpa-psk" => Ok(SecurityProtocol::WPA2), + "wpa-eap" => Ok(SecurityProtocol::WPA3Personal), + "sae" => Ok(SecurityProtocol::WPA2Enterprise), + "wpa-eap-suite-b192" => Ok(SecurityProtocol::WPA2Enterprise), + "none" => Ok(SecurityProtocol::WEP), + _ => Err(NmError::UnsupportedSecurityProtocol(value.to_string())), + } + } +} + +impl NmKeyManagement { + pub fn as_str(&self) -> &str { + &self.0.as_str() + } +} + +impl fmt::Display for NmKeyManagement { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", &self.0) + } +} + +#[derive(Debug, PartialEq)] +pub struct NmMethod(pub String); + +impl Default for NmMethod { + fn default() -> Self { + NmMethod("auto".to_string()) + } +} + +impl NmMethod { + pub fn as_str(&self) -> &str { + &self.0.as_str() + } +} + +impl TryFrom for IpMethod { + type Error = NmError; + + fn try_from(value: NmMethod) -> Result { + match value.as_str() { + "auto" => Ok(IpMethod::Auto), + "manual" => Ok(IpMethod::Manual), + "disabled" => Ok(IpMethod::Disabled), + "link-local" => Ok(IpMethod::LinkLocal), + _ => Err(NmError::UnsupportedIpMethod(value.to_string())), + } + } +} + +impl fmt::Display for NmMethod { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", &self.0) + } +} + +#[derive(Debug, Default, PartialEq)] +pub struct NmIp4Config { + pub addresses: Vec<(String, u32)>, + pub nameservers: Vec, + pub gateway: Option, + pub method: NmMethod, +} diff --git a/rust/agama-network/src/nm/proxies.rs b/rust/agama-network/src/nm/proxies.rs new file mode 100644 index 0000000000..32c3f0f833 --- /dev/null +++ b/rust/agama-network/src/nm/proxies.rs @@ -0,0 +1,585 @@ +//! D-Bus interface proxy for: `org.freedesktop.NetworkManager` +//! +//! This code was generated by `zbus-xmlgen` `3.1.0` from DBus introspection data. +//! +//! These D-Bus objects implements +//! [standard D-Bus interfaces](https://dbus.freedesktop.org/doc/dbus-specification.html), +//! (`org.freedesktop.DBus.*`) for which the following zbus proxies can be used: +//! +//! * [`zbus::fdo::PropertiesProxy`] +//! * [`zbus::fdo::IntrospectableProxy`] +//! +//! …consequently `zbus-xmlgen` did not generate code for the above interfaces. +//! Also some proxies can be used against multiple services when they share interface. + +use zbus::dbus_proxy; + +#[dbus_proxy( + interface = "org.freedesktop.NetworkManager", + default_service = "org.freedesktop.NetworkManager", + default_path = "/org/freedesktop/NetworkManager", + gen_blocking = false +)] +trait NetworkManager { + /// ActivateConnection method + fn activate_connection( + &self, + connection: &zbus::zvariant::ObjectPath<'_>, + device: &zbus::zvariant::ObjectPath<'_>, + specific_object: &zbus::zvariant::ObjectPath<'_>, + ) -> zbus::Result; + + /// AddAndActivateConnection method + fn add_and_activate_connection( + &self, + connection: std::collections::HashMap< + &str, + std::collections::HashMap<&str, zbus::zvariant::Value<'_>>, + >, + device: &zbus::zvariant::ObjectPath<'_>, + specific_object: &zbus::zvariant::ObjectPath<'_>, + ) -> zbus::Result<( + zbus::zvariant::OwnedObjectPath, + zbus::zvariant::OwnedObjectPath, + )>; + + /// AddAndActivateConnection2 method + fn add_and_activate_connection2( + &self, + connection: std::collections::HashMap< + &str, + std::collections::HashMap<&str, zbus::zvariant::Value<'_>>, + >, + device: &zbus::zvariant::ObjectPath<'_>, + specific_object: &zbus::zvariant::ObjectPath<'_>, + options: std::collections::HashMap<&str, zbus::zvariant::Value<'_>>, + ) -> zbus::Result<( + zbus::zvariant::OwnedObjectPath, + zbus::zvariant::OwnedObjectPath, + std::collections::HashMap, + )>; + + /// CheckConnectivity method + fn check_connectivity(&self) -> zbus::Result; + + /// CheckpointAdjustRollbackTimeout method + fn checkpoint_adjust_rollback_timeout( + &self, + checkpoint: &zbus::zvariant::ObjectPath<'_>, + add_timeout: u32, + ) -> zbus::Result<()>; + + /// CheckpointCreate method + fn checkpoint_create( + &self, + devices: &[zbus::zvariant::ObjectPath<'_>], + rollback_timeout: u32, + flags: u32, + ) -> zbus::Result; + + /// CheckpointDestroy method + fn checkpoint_destroy(&self, checkpoint: &zbus::zvariant::ObjectPath<'_>) -> zbus::Result<()>; + + /// CheckpointRollback method + fn checkpoint_rollback( + &self, + checkpoint: &zbus::zvariant::ObjectPath<'_>, + ) -> zbus::Result>; + + /// DeactivateConnection method + fn deactivate_connection( + &self, + active_connection: &zbus::zvariant::ObjectPath<'_>, + ) -> zbus::Result<()>; + + /// Enable method + fn enable(&self, enable: bool) -> zbus::Result<()>; + + /// GetAllDevices method + fn get_all_devices(&self) -> zbus::Result>; + + /// GetDeviceByIpIface method + fn get_device_by_ip_iface(&self, iface: &str) -> zbus::Result; + + /// GetDevices method + fn get_devices(&self) -> zbus::Result>; + + /// GetLogging method + fn get_logging(&self) -> zbus::Result<(String, String)>; + + /// GetPermissions method + fn get_permissions(&self) -> zbus::Result>; + + /// Reload method + fn reload(&self, flags: u32) -> zbus::Result<()>; + + /// SetLogging method + fn set_logging(&self, level: &str, domains: &str) -> zbus::Result<()>; + + /// Sleep method + fn sleep(&self, sleep: bool) -> zbus::Result<()>; + + /// CheckPermissions signal + #[dbus_proxy(signal)] + fn check_permissions(&self) -> zbus::Result<()>; + + /// DeviceAdded signal + #[dbus_proxy(signal)] + fn device_added(&self, device_path: zbus::zvariant::ObjectPath<'_>) -> zbus::Result<()>; + + /// DeviceRemoved signal + #[dbus_proxy(signal)] + fn device_removed(&self, device_path: zbus::zvariant::ObjectPath<'_>) -> zbus::Result<()>; + + /// ActivatingConnection property + #[dbus_proxy(property)] + fn activating_connection(&self) -> zbus::Result; + + /// ActiveConnections property + #[dbus_proxy(property)] + fn active_connections(&self) -> zbus::Result>; + + /// AllDevices property + #[dbus_proxy(property)] + fn all_devices(&self) -> zbus::Result>; + + /// Capabilities property + #[dbus_proxy(property)] + fn capabilities(&self) -> zbus::Result>; + + /// Checkpoints property + #[dbus_proxy(property)] + fn checkpoints(&self) -> zbus::Result>; + + /// Connectivity property + #[dbus_proxy(property)] + fn connectivity(&self) -> zbus::Result; + + /// ConnectivityCheckAvailable property + #[dbus_proxy(property)] + fn connectivity_check_available(&self) -> zbus::Result; + + /// ConnectivityCheckEnabled property + #[dbus_proxy(property)] + fn connectivity_check_enabled(&self) -> zbus::Result; + fn set_connectivity_check_enabled(&self, value: bool) -> zbus::Result<()>; + + /// ConnectivityCheckUri property + #[dbus_proxy(property)] + fn connectivity_check_uri(&self) -> zbus::Result; + + /// Devices property + #[dbus_proxy(property)] + fn devices(&self) -> zbus::Result>; + + /// GlobalDnsConfiguration property + #[dbus_proxy(property)] + fn global_dns_configuration( + &self, + ) -> zbus::Result>; + fn set_global_dns_configuration( + &self, + value: std::collections::HashMap<&str, zbus::zvariant::Value<'_>>, + ) -> zbus::Result<()>; + + /// Metered property + #[dbus_proxy(property)] + fn metered(&self) -> zbus::Result; + + /// NetworkingEnabled property + #[dbus_proxy(property)] + fn networking_enabled(&self) -> zbus::Result; + + /// PrimaryConnection property + #[dbus_proxy(property)] + fn primary_connection(&self) -> zbus::Result; + + /// PrimaryConnectionType property + #[dbus_proxy(property)] + fn primary_connection_type(&self) -> zbus::Result; + + /// RadioFlags property + #[dbus_proxy(property)] + fn radio_flags(&self) -> zbus::Result; + + /// Startup property + #[dbus_proxy(property)] + fn startup(&self) -> zbus::Result; + + /// State property + #[dbus_proxy(property)] + fn state(&self) -> zbus::Result; + + /// Version property + #[dbus_proxy(property)] + fn version(&self) -> zbus::Result; + + /// VersionInfo property + #[dbus_proxy(property)] + fn version_info(&self) -> zbus::Result>; + + /// WimaxEnabled property + #[dbus_proxy(property)] + fn wimax_enabled(&self) -> zbus::Result; + fn set_wimax_enabled(&self, value: bool) -> zbus::Result<()>; + + /// WimaxHardwareEnabled property + #[dbus_proxy(property)] + fn wimax_hardware_enabled(&self) -> zbus::Result; + + /// WirelessEnabled property + #[dbus_proxy(property)] + fn wireless_enabled(&self) -> zbus::Result; + fn set_wireless_enabled(&self, value: bool) -> zbus::Result<()>; + + /// WirelessHardwareEnabled property + #[dbus_proxy(property)] + fn wireless_hardware_enabled(&self) -> zbus::Result; + + /// WwanEnabled property + #[dbus_proxy(property)] + fn wwan_enabled(&self) -> zbus::Result; + fn set_wwan_enabled(&self, value: bool) -> zbus::Result<()>; + + /// WwanHardwareEnabled property + #[dbus_proxy(property)] + fn wwan_hardware_enabled(&self) -> zbus::Result; +} + +/// # DBus interface proxies for: `org.freedesktop.NetworkManager.Device` +/// +/// This code was generated by `zbus-xmlgen` `3.1.0` from DBus introspection data. +#[dbus_proxy( + interface = "org.freedesktop.NetworkManager.Device", + default_service = "org.freedesktop.NetworkManager", + default_path = "/org/freedesktop/NetworkManager/Devices/1" +)] +trait Device { + /// Delete method + fn delete(&self) -> zbus::Result<()>; + + /// Disconnect method + fn disconnect(&self) -> zbus::Result<()>; + + /// GetAppliedConnection method + fn get_applied_connection( + &self, + flags: u32, + ) -> zbus::Result<( + std::collections::HashMap< + String, + std::collections::HashMap, + >, + u64, + )>; + + /// Reapply method + fn reapply( + &self, + connection: std::collections::HashMap< + &str, + std::collections::HashMap<&str, zbus::zvariant::Value<'_>>, + >, + version_id: u64, + flags: u32, + ) -> zbus::Result<()>; + + /// ActiveConnection property + #[dbus_proxy(property)] + fn active_connection(&self) -> zbus::Result; + + /// Autoconnect property + #[dbus_proxy(property)] + fn autoconnect(&self) -> zbus::Result; + fn set_autoconnect(&self, value: bool) -> zbus::Result<()>; + + /// AvailableConnections property + #[dbus_proxy(property)] + fn available_connections(&self) -> zbus::Result>; + + /// Capabilities property + #[dbus_proxy(property)] + fn capabilities(&self) -> zbus::Result; + + /// DeviceType property + #[dbus_proxy(property)] + fn device_type(&self) -> zbus::Result; + + /// Dhcp4Config property + #[dbus_proxy(property)] + fn dhcp4_config(&self) -> zbus::Result; + + /// Dhcp6Config property + #[dbus_proxy(property)] + fn dhcp6_config(&self) -> zbus::Result; + + /// Driver property + #[dbus_proxy(property)] + fn driver(&self) -> zbus::Result; + + /// DriverVersion property + #[dbus_proxy(property)] + fn driver_version(&self) -> zbus::Result; + + /// FirmwareMissing property + #[dbus_proxy(property)] + fn firmware_missing(&self) -> zbus::Result; + + /// FirmwareVersion property + #[dbus_proxy(property)] + fn firmware_version(&self) -> zbus::Result; + + /// HwAddress property + #[dbus_proxy(property)] + fn hw_address(&self) -> zbus::Result; + + /// Interface property + #[dbus_proxy(property)] + fn interface(&self) -> zbus::Result; + + /// InterfaceFlags property + #[dbus_proxy(property)] + fn interface_flags(&self) -> zbus::Result; + + /// Ip4Address property + #[dbus_proxy(property)] + fn ip4_address(&self) -> zbus::Result; + + /// Ip4Config property + #[dbus_proxy(property)] + fn ip4_config(&self) -> zbus::Result; + + /// Ip4Connectivity property + #[dbus_proxy(property)] + fn ip4_connectivity(&self) -> zbus::Result; + + /// Ip6Config property + #[dbus_proxy(property)] + fn ip6_config(&self) -> zbus::Result; + + /// Ip6Connectivity property + #[dbus_proxy(property)] + fn ip6_connectivity(&self) -> zbus::Result; + + /// IpInterface property + #[dbus_proxy(property)] + fn ip_interface(&self) -> zbus::Result; + + /// LldpNeighbors property + #[dbus_proxy(property)] + fn lldp_neighbors( + &self, + ) -> zbus::Result>>; + + /// Managed property + #[dbus_proxy(property)] + fn managed(&self) -> zbus::Result; + fn set_managed(&self, value: bool) -> zbus::Result<()>; + + /// Metered property + #[dbus_proxy(property)] + fn metered(&self) -> zbus::Result; + + /// Mtu property + #[dbus_proxy(property)] + fn mtu(&self) -> zbus::Result; + + /// NmPluginMissing property + #[dbus_proxy(property)] + fn nm_plugin_missing(&self) -> zbus::Result; + + /// Path property + #[dbus_proxy(property)] + fn path(&self) -> zbus::Result; + + /// PhysicalPortId property + #[dbus_proxy(property)] + fn physical_port_id(&self) -> zbus::Result; + + /// Ports property + #[dbus_proxy(property)] + fn ports(&self) -> zbus::Result>; + + /// Real property + #[dbus_proxy(property)] + fn real(&self) -> zbus::Result; + + /// State property + #[dbus_proxy(property)] + fn state(&self) -> zbus::Result; + + /// StateReason property + #[dbus_proxy(property)] + fn state_reason(&self) -> zbus::Result<(u32, u32)>; + + /// Udi property + #[dbus_proxy(property)] + fn udi(&self) -> zbus::Result; +} + +/// # DBus interface proxy for: `org.freedesktop.NetworkManager.Settings` +/// +/// This code was generated by `zbus-xmlgen` `3.1.0` from DBus introspection data. +#[dbus_proxy( + interface = "org.freedesktop.NetworkManager.Settings", + default_service = "org.freedesktop.NetworkManager", + default_path = "/org/freedesktop/NetworkManager/Settings", + gen_blocking = false +)] +trait Settings { + /// AddConnection method + fn add_connection( + &self, + connection: std::collections::HashMap< + &str, + std::collections::HashMap<&str, zbus::zvariant::Value<'_>>, + >, + ) -> zbus::Result; + + /// AddConnection2 method + fn add_connection2( + &self, + settings: std::collections::HashMap< + &str, + std::collections::HashMap<&str, zbus::zvariant::Value<'_>>, + >, + flags: u32, + args: std::collections::HashMap<&str, zbus::zvariant::Value<'_>>, + ) -> zbus::Result<( + zbus::zvariant::OwnedObjectPath, + std::collections::HashMap, + )>; + + /// AddConnectionUnsaved method + fn add_connection_unsaved( + &self, + connection: std::collections::HashMap< + &str, + std::collections::HashMap<&str, zbus::zvariant::Value<'_>>, + >, + ) -> zbus::Result; + + /// GetConnectionByUuid method + fn get_connection_by_uuid(&self, uuid: &str) -> zbus::Result; + + /// ListConnections method + fn list_connections(&self) -> zbus::Result>; + + /// LoadConnections method + fn load_connections(&self, filenames: &[&str]) -> zbus::Result<(bool, Vec)>; + + /// ReloadConnections method + fn reload_connections(&self) -> zbus::Result; + + /// SaveHostname method + fn save_hostname(&self, hostname: &str) -> zbus::Result<()>; + + /// ConnectionRemoved signal + #[dbus_proxy(signal)] + fn connection_removed(&self, connection: zbus::zvariant::ObjectPath<'_>) -> zbus::Result<()>; + + /// NewConnection signal + #[dbus_proxy(signal)] + fn new_connection(&self, connection: zbus::zvariant::ObjectPath<'_>) -> zbus::Result<()>; + + /// CanModify property + #[dbus_proxy(property)] + fn can_modify(&self) -> zbus::Result; + + /// Connections property + #[dbus_proxy(property)] + fn connections(&self) -> zbus::Result>; + + /// Hostname property + #[dbus_proxy(property)] + fn hostname(&self) -> zbus::Result; +} +/// # DBus interface proxy for: `org.freedesktop.NetworkManager.Settings.Connection` +/// +/// This code was generated by `zbus-xmlgen` `3.1.0` from DBus introspection data. +#[dbus_proxy( + interface = "org.freedesktop.NetworkManager.Settings.Connection", + default_service = "org.freedesktop.NetworkManager", + default_path = "/org/freedesktop/NetworkManager/Settings/1", + gen_blocking = false +)] +trait Connection { + /// ClearSecrets method + fn clear_secrets(&self) -> zbus::Result<()>; + + /// Delete method + fn delete(&self) -> zbus::Result<()>; + + /// GetSecrets method + fn get_secrets( + &self, + setting_name: &str, + ) -> zbus::Result< + std::collections::HashMap< + String, + std::collections::HashMap, + >, + >; + + /// GetSettings method + fn get_settings( + &self, + ) -> zbus::Result< + std::collections::HashMap< + String, + std::collections::HashMap, + >, + >; + + /// Save method + fn save(&self) -> zbus::Result<()>; + + /// Update method + fn update( + &self, + properties: std::collections::HashMap< + &str, + std::collections::HashMap<&str, zbus::zvariant::Value<'_>>, + >, + ) -> zbus::Result<()>; + + /// Update2 method + fn update2( + &self, + settings: std::collections::HashMap< + &str, + std::collections::HashMap<&str, zbus::zvariant::Value<'_>>, + >, + flags: u32, + args: std::collections::HashMap<&str, zbus::zvariant::Value<'_>>, + ) -> zbus::Result>; + + /// UpdateUnsaved method + fn update_unsaved( + &self, + properties: std::collections::HashMap< + &str, + std::collections::HashMap<&str, zbus::zvariant::Value<'_>>, + >, + ) -> zbus::Result<()>; + + /// Removed signal + #[dbus_proxy(signal)] + fn removed(&self) -> zbus::Result<()>; + + /// Updated signal + #[dbus_proxy(signal)] + fn updated(&self) -> zbus::Result<()>; + + /// Filename property + #[dbus_proxy(property)] + fn filename(&self) -> zbus::Result; + + /// Flags property + #[dbus_proxy(property)] + fn flags(&self) -> zbus::Result; + + /// Unsaved property + #[dbus_proxy(property)] + fn unsaved(&self) -> zbus::Result; +} diff --git a/rust/agama-network/src/system.rs b/rust/agama-network/src/system.rs new file mode 100644 index 0000000000..f173b4e7cb --- /dev/null +++ b/rust/agama-network/src/system.rs @@ -0,0 +1,106 @@ +use crate::dbus::Tree; +use crate::{model::Connection, nm::NetworkManagerAdapter, Action, Adapter, NetworkState}; +use agama_lib::error::ServiceError; +use std::error::Error; +use std::sync::mpsc::{channel, Receiver, Sender}; + +/// Represents the network system, wrapping a [NetworkState] and setting up the D-Bus tree. +pub struct NetworkSystem { + /// Network state + pub state: NetworkState, + /// Side of the channel to send actions. + actions_tx: Sender, + actions_rx: Receiver, + tree: Tree, +} + +impl NetworkSystem { + pub fn new(state: NetworkState, conn: zbus::Connection) -> Self { + let (actions_tx, actions_rx) = channel(); + let tree = Tree::new(conn, actions_tx.clone()); + Self { + state, + actions_tx, + actions_rx, + tree, + } + } + + /// Reads the network configuration using the NetworkManager adapter. + /// + /// * `conn`: connection where self will be exposed. Another connection will be made internally + /// to talk with NetworkManager (which may be on a different bus even). + pub async fn from_network_manager( + conn: zbus::Connection, + ) -> Result> { + let adapter = NetworkManagerAdapter::from_system() + .await + .expect("Could not connect to NetworkManager to read the configuration."); + let state = adapter.read()?; + Ok(Self::new(state, conn)) + } + + /// Writes the network configuration to NetworkManager. + pub async fn to_network_manager(&mut self) -> Result<(), Box> { + let adapter = NetworkManagerAdapter::from_system() + .await + .expect("Could not connect to NetworkManager to write the changes."); + adapter.write(&self.state)?; + self.state = adapter.read()?; + Ok(()) + } + + /// Returns a clone of the [Sender](https://doc.rust-lang.org/std/sync/mpsc/struct.Sender.html) to execute + /// [actions](Action). + pub fn actions_tx(&self) -> Sender { + self.actions_tx.clone() + } + + /// Populates the D-Bus tree with the known devices and connections. + pub async fn setup(&mut self) -> Result<(), ServiceError> { + self.tree + .set_connections(&self.state.connections) + .await?; + self.tree.set_devices(&self.state.devices).await?; + Ok(()) + } + + /// Process incoming actions. + /// + /// This function is expected to be executed on a separate thread. + pub async fn listen(&mut self) { + while let Ok(action) = self.actions_rx.recv() { + if let Err(error) = self.dispatch_action(action).await { + eprintln!("Could not process the action: {}", error); + } + } + } + + /// Dispatch an action. + pub async fn dispatch_action(&mut self, action: Action) -> Result<(), Box> { + match action { + Action::AddConnection(name, ty) => { + let conn = Connection::new(name, ty); + self.tree.add_connection(&conn).await?; + self.state.add_connection(conn)?; + } + Action::UpdateConnection(conn) => { + self.state.update_connection(conn)?; + } + Action::RemoveConnection(uuid) => { + self.tree.remove_connection(uuid).await?; + self.state.remove_connection(uuid)?; + } + Action::Apply => { + self.to_network_manager().await?; + // TODO: re-creating the tree is kind of brute-force and it sends signals about + // adding/removing interfaces. We should add/update/delete objects as needed. + self.tree + .set_connections(&self.state.connections) + .await?; + } + } + + Ok(()) + } +} diff --git a/rust/package/agama-cli.changes b/rust/package/agama-cli.changes index 1539f2645d..fe96344e95 100644 --- a/rust/package/agama-cli.changes +++ b/rust/package/agama-cli.changes @@ -1,3 +1,9 @@ +------------------------------------------------------------------- +Thu Jun 1 08:14:14 UTC 2023 - Imobach Gonzalez Sosa + +- Add a localization D-Bus service (gh#openSUSE/agama#533). +- Add a network D-Bus service (gh#openSUSE/agama#571). + ------------------------------------------------------------------- Tue May 23 11:51:26 UTC 2023 - Martin Vidner diff --git a/service/share/dbus.conf b/service/share/dbus.conf index 47a6ef1f4c..0c64fd6b2c 100644 --- a/service/share/dbus.conf +++ b/service/share/dbus.conf @@ -39,6 +39,7 @@ + @@ -46,6 +47,7 @@ +