crabshell_services/network/
saved.rs1use std::cell::Cell;
6use std::collections::HashMap;
7use std::rc::Rc;
8
9use crabshell_reactive::Signal;
10use serde_json::Value as Json;
11use zbus::{Connection, Message, OwnedObjectPath, Variant};
12
13use super::{NM, by_number};
14use crate::Shared;
15use crate::dbus::{self, CALL_TIMEOUT, Daemon, Props, set_changed};
16
17pub(super) const SETTINGS: &str = "org.freedesktop.NetworkManager.Settings";
18pub(super) const SETTINGS_PATH: &str = "/org/freedesktop/NetworkManager/Settings";
19pub(super) const SAVED: &str = "org.freedesktop.NetworkManager.Settings.Connection";
20
21const MAX_SAVED: usize = 256;
24const MAX_REPLY: usize = 64 * 1024;
27const MAX_KEPT: usize = 1024 * 1024;
31
32#[derive(Clone, Debug, PartialEq)]
35pub struct SavedConnection {
36 pub uuid: String,
39 pub id: String,
41 pub kind: String,
45 pub settings: Json,
50}
51
52impl SavedConnection {
53 fn named(settings: &HashMap<String, Props>) -> SavedConnection {
55 let about = settings.get("connection");
56 let text = |name| about.map_or(String::new(), |props| dbus::text(props, name));
57 SavedConnection {
58 uuid: text("uuid"),
59 id: text("id"),
60 kind: text("type"),
61 settings: Json::Null,
62 }
63 }
64
65 fn weight(&self) -> usize {
67 size_of::<SavedConnection>() + self.uuid.len() + self.id.len() + self.kind.len()
68 }
69}
70
71fn settings_json(settings: &HashMap<String, Props>, cost: &mut usize) -> Json {
74 let mut groups = serde_json::Map::new();
75 for (group, props) in settings {
76 let mut entries = serde_json::Map::new();
77 for (name, value) in props {
78 *cost += size_of::<String>() + name.len();
79 entries.insert(name.clone(), json(value, cost));
80 }
81 *cost += size_of::<String>() + group.len() + size_of::<Json>();
82 groups.insert(group.clone(), Json::Object(entries));
83 }
84 *cost += size_of::<Json>();
85 Json::Object(groups)
86}
87
88pub(super) fn json(value: &Variant<'_>, cost: &mut usize) -> Json {
93 let node = match value {
94 Variant::Variant(inner) => return json(inner, cost),
96 Variant::U8(n) => Json::from(*n),
97 Variant::Bool(b) => Json::from(*b),
98 Variant::I16(n) => Json::from(*n),
99 Variant::U16(n) => Json::from(*n),
100 Variant::I32(n) => Json::from(*n),
101 Variant::U32(n) => Json::from(*n),
102 Variant::I64(n) => Json::from(*n),
103 Variant::U64(n) => Json::from(*n),
104 Variant::F64(n) => Json::from(*n),
105 Variant::Str(text) => text_json(text.as_str().to_owned(), cost),
106 Variant::Signature(signature) => text_json(signature.to_string(), cost),
107 Variant::ObjectPath(path) => text_json(path.as_str().to_owned(), cost),
108 Variant::Array(array) => array.inner().iter().map(|item| json(item, cost)).collect(),
109 Variant::Structure(structure) => {
110 let fields = structure.fields().iter();
111 fields.map(|field| json(field, cost)).collect()
112 }
113 Variant::Dict(dict) => {
114 let mut entries = serde_json::Map::new();
115 for (key, value) in dict.iter() {
116 let key = match key {
117 Variant::Str(text) => text.as_str().to_owned(),
118 key => json(key, &mut 0).to_string(),
119 };
120 *cost += size_of::<String>() + key.len();
121 entries.insert(key, json(value, cost));
122 }
123 Json::Object(entries)
124 }
125 Variant::Fd(_) => Json::Null,
126 };
127 *cost += size_of::<Json>();
128 node
129}
130
131fn text_json(text: String, cost: &mut usize) -> Json {
133 *cost += text.len();
134 Json::String(text)
135}
136
137pub(super) struct SavedConnections {
139 shared: Rc<Shared>,
140 pub(super) list: Signal<Vec<SavedConnection>>,
141 followed: Cell<bool>,
142 generation: Cell<u64>,
143}
144
145impl SavedConnections {
146 pub(super) fn new(shared: Rc<Shared>) -> Rc<SavedConnections> {
147 let list = shared.root.signal(Vec::new());
148 Rc::new(SavedConnections {
149 shared,
150 list,
151 followed: Cell::new(false),
152 generation: Cell::new(0),
153 })
154 }
155
156 pub(super) fn follow(self: &Rc<Self>) {
158 if self.followed.replace(true) {
159 return;
160 }
161 if let Some(address) = self.shared.config.system_bus.clone() {
164 let follow = dbus::follow(Rc::clone(&self.shared), Rc::clone(self), address);
165 self.shared.spawn(follow);
166 }
167 }
168}
169
170impl Daemon for SavedConnections {
171 const NAME: &'static str = NM;
172
173 fn rules() -> Vec<String> {
175 vec![
176 format!("type='signal',sender='{NM}',path='{SETTINGS_PATH}',interface='{SETTINGS}'"),
177 format!("type='signal',sender='{NM}',interface='{SAVED}',member='Updated'"),
178 ]
179 }
180
181 fn alive(&self) -> bool {
182 self.list.try_with(|_| ()).is_some()
183 }
184
185 fn clear(&self) -> u64 {
186 set_changed(self.list, Vec::new());
187 self.generation.set(self.generation.get() + 1);
188 self.generation.get()
189 }
190
191 async fn read(self: Rc<Self>, connection: Connection, generation: u64) {
192 let shared = &self.shared;
193 let call = connection.call_method(
194 Some(NM),
195 SETTINGS_PATH,
196 Some(SETTINGS),
197 "ListConnections",
198 &(),
199 );
200 let listed = async {
201 let reply = shared.timeout(CALL_TIMEOUT, call).await?;
202 reply.body().deserialize::<Vec<OwnedObjectPath>>()
203 };
204 let mut paths = match listed.await {
205 Ok(paths) => paths,
206 Err(error) => {
207 return tracing::warn!(%error, "couldn't list NetworkManager's saved connections");
208 }
209 };
210 paths.sort_by(|a, b| by_number(a.as_str(), b.as_str()));
212 let (mut list, mut room) = (Vec::new(), MAX_KEPT);
213 let (mut without_settings, mut left_out) = (0, 0);
214 for path in paths.iter().take(MAX_SAVED).map(|path| path.as_str()) {
215 let call = connection.call_method(Some(NM), path, Some(SAVED), "GetSettings", &());
216 let reply = match shared.timeout(CALL_TIMEOUT, call).await {
217 Ok(reply) => reply,
218 Err(error) => {
220 tracing::info!(%error, path, "couldn't read a saved connection");
221 continue;
222 }
223 };
224 let body = reply.body();
225 if body.len() > MAX_REPLY {
226 tracing::warn!(
227 path,
228 bytes = body.len(),
229 "a saved connection too big; left out"
230 );
231 continue;
232 }
233 let Ok(settings) = body.deserialize::<HashMap<String, Props>>() else {
234 tracing::warn!(
235 path,
236 "a saved connection's settings that don't parse; left out"
237 );
238 continue;
239 };
240 let mut saved = SavedConnection::named(&settings);
241 let Some(left) = room.checked_sub(saved.weight()) else {
242 left_out += 1;
243 continue;
244 };
245 room = left;
246 let mut cost = 0;
247 let json = settings_json(&settings, &mut cost);
248 match room.checked_sub(cost) {
249 Some(left) => (room, saved.settings) = (left, json),
250 None => without_settings += 1,
251 }
252 list.push(saved);
253 }
254 if without_settings + left_out > 0 {
255 let what = "saved connections past 1 MiB listed without settings, or left out";
256 tracing::warn!(without_settings, left_out, "{what}");
257 }
258 if self.generation.get() == generation {
259 set_changed(self.list, list);
260 }
261 }
262
263 fn signal(self: &Rc<Self>, _: &Connection, _: &Message) {}
264
265 fn reads_again(&self, _: &Message) -> bool {
268 true
269 }
270}