feat(network): support for eap peap networks

This commit is contained in:
Ashley Wulber 2025-04-18 11:41:26 -04:00 committed by Ashley Wulber
parent 4f1ef57fe2
commit 31afd3fa8b
4 changed files with 265 additions and 129 deletions

View file

@ -3,6 +3,7 @@ airplane-mode = Airplane mode
airplane-mode-on = Airplane mode is on airplane-mode-on = Airplane mode is on
turn-off-airplane-mode = Turn off to enable Wi-Fi, Bluetooth and mobile broadband. turn-off-airplane-mode = Turn off to enable Wi-Fi, Bluetooth and mobile broadband.
wifi = Wi-Fi wifi = Wi-Fi
identity = Identity
ipv4 = IPv4 address ipv4 = IPv4 address
ipv6 = IPv6 address ipv6 = IPv6 address
mac = MAC mac = MAC

View file

@ -34,11 +34,14 @@ use zbus::Connection;
use crate::{ use crate::{
config, fl, config, fl,
network_manager::{ network_manager::{
active_conns::active_conns_subscription, available_wifi::AccessPoint, active_conns::active_conns_subscription,
current_networks::ActiveConnectionInfo, devices::devices_subscription, available_wifi::{AccessPoint, NetworkType},
hw_address::HwAddress, network_manager_subscription, current_networks::ActiveConnectionInfo,
wireless_enabled::wireless_enabled_subscription, NetworkManagerEvent, devices::devices_subscription,
NetworkManagerRequest, NetworkManagerState, hw_address::HwAddress,
network_manager_subscription,
wireless_enabled::wireless_enabled_subscription,
NetworkManagerEvent, NetworkManagerRequest, NetworkManagerState,
}, },
}; };
@ -50,6 +53,7 @@ pub fn run() -> cosmic::iced::Result {
enum NewConnectionState { enum NewConnectionState {
EnterPassword { EnterPassword {
access_point: AccessPoint, access_point: AccessPoint,
identity: String,
password: String, password: String,
password_hidden: bool, password_hidden: bool,
}, },
@ -235,6 +239,7 @@ pub(crate) enum Message {
SelectWirelessAccessPoint(AccessPoint), SelectWirelessAccessPoint(AccessPoint),
CancelNewConnection, CancelNewConnection,
Password(String), Password(String),
Identity(String),
SubmitPassword, SubmitPassword,
Frame(Instant), Frame(Instant),
Token(TokenUpdate), Token(TokenUpdate),
@ -331,41 +336,57 @@ impl cosmic::Application for CosmicNetworkApplet {
success, success,
req, req,
} => { } => {
if let NetworkManagerRequest::SelectAccessPoint(ssid, hw_address) = &req { if let NetworkManagerRequest::SelectAccessPoint(
let conn_match = self ssid,
.new_connection hw_address,
.as_ref() _network_type,
.map(|c| c.ssid() == ssid && c.hw_address() == *hw_address) ) = &req
.unwrap_or_default(); {
if conn_match && success { let conn_match = self
if let Some(s) = .new_connection
state.active_conns.iter_mut().find(|ap| &ap.name() == ssid && ap.hw_address() == *hw_address) .as_ref()
{ .map(|c| c.ssid() == ssid && c.hw_address() == *hw_address)
match s { .unwrap_or_default();
ActiveConnectionInfo::WiFi { state, .. } => {
*state = ActiveConnectionState::Activated; if conn_match && success {
} if let Some(s) = state
_ => {} .active_conns
}; .iter_mut()
.find(|ap| &ap.name() == ssid && ap.hw_address() == *hw_address)
{
match s {
ActiveConnectionInfo::WiFi { state, .. } => {
*state = ActiveConnectionState::Activated;
} }
self.failed_known_ssids.remove(ssid); _ => {}
self.new_connection = None; };
self.show_visible_networks = false; }
} else if !matches!( self.failed_known_ssids.remove(ssid);
&self.new_connection, self.new_connection = None;
Some(NewConnectionState::EnterPassword { .. }) self.show_visible_networks = false;
) } else if !matches!(
{ &self.new_connection,
self.failed_known_ssids.insert(ssid.clone()); Some(NewConnectionState::EnterPassword { .. })
} ) {
} else if let NetworkManagerRequest::Password(ssid, _, hw_address) = &req { self.failed_known_ssids.insert(ssid.clone());
if let Some(NewConnectionState::Waiting(access_point)) = }
self.new_connection.clone() } else if let NetworkManagerRequest::Authenticate {
{ ssid,
if !success && ssid == &access_point.ssid && *hw_address == access_point.hw_address { identity: _,
self.new_connection = password: _,
Some(NewConnectionState::Failure(access_point.clone())); hw_address,
} else { } = &req
{
if let Some(NewConnectionState::Waiting(access_point)) =
self.new_connection.clone()
{
if !success
&& ssid == &access_point.ssid
&& *hw_address == access_point.hw_address
{
self.new_connection =
Some(NewConnectionState::Failure(access_point.clone()));
} else {
self.new_connection = None; self.new_connection = None;
self.show_visible_networks = false; self.show_visible_networks = false;
} }
@ -412,13 +433,19 @@ impl cosmic::Application for CosmicNetworkApplet {
let _ = tx.unbounded_send(NetworkManagerRequest::SelectAccessPoint( let _ = tx.unbounded_send(NetworkManagerRequest::SelectAccessPoint(
access_point.ssid.clone(), access_point.ssid.clone(),
access_point.hw_address, access_point.hw_address,
access_point.network_type,
)); ));
self.new_connection = Some(NewConnectionState::EnterPassword { if matches!(access_point.network_type, NetworkType::Open) {
access_point, self.new_connection = Some(NewConnectionState::Waiting(access_point));
password: String::new(), } else {
password_hidden: true, self.new_connection = Some(NewConnectionState::EnterPassword {
}); access_point,
identity: String::new(),
password: String::new(),
password_hidden: true,
});
}
} }
Message::ToggleVisibleNetworks => { Message::ToggleVisibleNetworks => {
self.new_connection = None; self.new_connection = None;
@ -450,19 +477,24 @@ impl cosmic::Application for CosmicNetworkApplet {
if let Some(NewConnectionState::EnterPassword { if let Some(NewConnectionState::EnterPassword {
password, password,
access_point, access_point,
identity,
.. ..
}) = self.new_connection.take() }) = self.new_connection.take()
{ {
let _ = tx.unbounded_send(NetworkManagerRequest::Password( let is_enterprise: bool = matches!(access_point.network_type, NetworkType::EAP);
access_point.ssid.clone(),
password, let _ = tx.unbounded_send(NetworkManagerRequest::Authenticate {
access_point.hw_address, ssid: access_point.ssid.clone(),
)); identity: is_enterprise.then(|| identity.clone()),
password: password,
hw_address: access_point.hw_address,
});
self.new_connection self.new_connection
.replace(NewConnectionState::Waiting(access_point)); .replace(NewConnectionState::Waiting(access_point));
}; };
} }
Message::ActivateKnownWifi(ssid, hw_address) => { Message::ActivateKnownWifi(ssid, hw_address) => {
let mut network_type = NetworkType::Open;
let tx = if let Some(tx) = self.nm_sender.as_ref() { let tx = if let Some(tx) = self.nm_sender.as_ref() {
if let Some(ap) = self if let Some(ap) = self
.nm_state .nm_state
@ -470,14 +502,18 @@ impl cosmic::Application for CosmicNetworkApplet {
.iter_mut() .iter_mut()
.find(|c| c.ssid == ssid && c.hw_address == hw_address) .find(|c| c.ssid == ssid && c.hw_address == hw_address)
{ {
network_type = ap.network_type;
ap.working = true; ap.working = true;
} }
tx tx
} else { } else {
return Task::none(); return Task::none();
}; };
let _ = let _ = tx.unbounded_send(NetworkManagerRequest::SelectAccessPoint(
tx.unbounded_send(NetworkManagerRequest::SelectAccessPoint(ssid, hw_address)); ssid,
hw_address,
network_type,
));
} }
Message::CancelNewConnection => { Message::CancelNewConnection => {
self.new_connection = None; self.new_connection = None;
@ -570,6 +606,13 @@ impl cosmic::Application for CosmicNetworkApplet {
cosmic::app::Action::Surface(a), cosmic::app::Action::Surface(a),
)); ));
} }
Message::Identity(new_identity) => {
if let Some(NewConnectionState::EnterPassword { identity, .. }) =
&mut self.new_connection
{
*identity = new_identity;
}
}
} }
Task::none() Task::none()
} }
@ -964,6 +1007,7 @@ impl cosmic::Application for CosmicNetworkApplet {
match new_conn_state { match new_conn_state {
NewConnectionState::EnterPassword { NewConnectionState::EnterPassword {
access_point, access_point,
identity,
password, password,
password_hidden, password_hidden,
} => { } => {
@ -978,30 +1022,38 @@ impl cosmic::Application for CosmicNetworkApplet {
.spacing(12), .spacing(12),
); );
content = content.push(id); content = content.push(id);
let enter_password_col = column![
text::body(fl!("enter-password")), let is_enterprise = matches!(access_point.network_type, NetworkType::EAP);
text_input::secure_input( let enter_password_col = column![]
"", .push_maybe(is_enterprise.then(|| text::body(fl!("identity"))))
password, .push_maybe(is_enterprise.then(|| {
Some(Message::TogglePasswordVisibility), text_input::text_input("", identity).on_input(Message::Identity)
*password_hidden, }))
.push(text::body(fl!("enter-password")))
.push(
text_input::secure_input(
"",
password,
Some(Message::TogglePasswordVisibility),
*password_hidden,
)
.on_input(Message::Password)
.on_paste(Message::Password)
.on_submit(|_| Message::SubmitPassword),
) )
.on_input(Message::Password) .push_maybe(
.on_paste(Message::Password) access_point.wps_push.then(|| {
.on_submit(|_| Message::SubmitPassword) container(text::body(fl!("router-wps-button"))).padding(8)
] }),
.push_maybe( )
access_point .push(
.wps_push row![
.then(|| container(text::body(fl!("router-wps-button"))).padding(8)), button::standard(fl!("cancel"))
) .on_press(Message::CancelNewConnection),
.push( button::suggested(fl!("connect")).on_press(Message::SubmitPassword)
row![ ]
button::standard(fl!("cancel")).on_press(Message::CancelNewConnection), .spacing(24),
button::suggested(fl!("connect")).on_press(Message::SubmitPassword) );
]
.spacing(24),
);
let col = let col =
padded_control(enter_password_col.spacing(8).align_x(Alignment::Center)) padded_control(enter_password_col.spacing(8).align_x(Alignment::Center))
.align_x(Alignment::Center); .align_x(Alignment::Center);

View file

@ -2,7 +2,10 @@
use cosmic_dbus_networkmanager::{ use cosmic_dbus_networkmanager::{
device::wireless::WirelessDevice, device::wireless::WirelessDevice,
interface::enums::{ApFlags, DeviceState}, interface::{
access_point::AccessPointProxy,
enums::{ApFlags, ApSecurityFlags, DeviceState},
},
}; };
use futures_util::StreamExt; use futures_util::StreamExt;
@ -42,7 +45,22 @@ pub async fn handle_wireless_device(
if access_point.strength > strength { if access_point.strength > strength {
continue; continue;
} }
} };
let proxy: &AccessPointProxy = ≈
let Ok(flags) = ap.rsn_flags().await else {
continue;
};
let network_type = if flags.intersects(ApSecurityFlags::KEY_MGMT_802_1X) {
NetworkType::EAP
} else if flags.intersects(ApSecurityFlags::KEY_MGMTPSK) {
NetworkType::PSK
} else if flags.is_empty() {
NetworkType::Open
} else {
continue;
};
aps.insert( aps.insert(
ssid.clone(), ssid.clone(),
AccessPoint { AccessPoint {
@ -56,6 +74,7 @@ pub async fn handle_wireless_device(
.and_then(|str_addr| HwAddress::from_str(str_addr)) .and_then(|str_addr| HwAddress::from_str(str_addr))
.unwrap_or_default(), .unwrap_or_default(),
wps_push, wps_push,
network_type,
}, },
); );
} }
@ -75,4 +94,15 @@ pub struct AccessPoint {
pub path: ObjectPath<'static>, pub path: ObjectPath<'static>,
pub hw_address: HwAddress, pub hw_address: HwAddress,
pub wps_push: bool, pub wps_push: bool,
pub network_type: NetworkType,
}
// TODO do we want to support eap methods other than peap in the applet?
// Then we'd need a dropdown for the eap method,
// and tls requires a cert instead of a password
#[derive(Debug, Clone, Copy)]
pub enum NetworkType {
Open,
PSK,
EAP,
} }

View file

@ -7,6 +7,7 @@ pub mod wireless_enabled;
use std::{collections::HashMap, fmt::Debug, time::Duration}; use std::{collections::HashMap, fmt::Debug, time::Duration};
use available_wifi::NetworkType;
use cosmic::{ use cosmic::{
iced::{self, Subscription}, iced::{self, Subscription},
iced_futures::stream, iced_futures::stream,
@ -186,39 +187,52 @@ async fn start_listening(
}; };
_ = output.send(response).await; _ = output.send(response).await;
} }
Some(NetworkManagerRequest::Password(ssid, password, hw_address)) => { Some(NetworkManagerRequest::Authenticate {
ssid,
identity,
password,
hw_address,
}) => {
let nm_state = NetworkManagerState::new(&conn).await.unwrap_or_default(); let nm_state = NetworkManagerState::new(&conn).await.unwrap_or_default();
let success = nm_state let mut success = true;
.connect_wifi(&conn, &ssid, Some(&password), hw_address) let err = nm_state
.await .connect_wifi(
.is_ok(); &conn,
&ssid,
let status = Some(NetworkManagerEvent::RequestResponse { identity.as_deref(),
req: NetworkManagerRequest::Password( Some(&password),
ssid.clone(),
password.clone(),
hw_address, hw_address,
), )
success, .await;
state: NetworkManagerState::new(&conn).await.unwrap_or_default(),
});
if let Some(e) = status { if let Err(err) = err {
_ = output.send(e).await; success = false;
} else { tracing::error!("{:?}", &err);
_ = output
.send(NetworkManagerEvent::RequestResponse {
req: NetworkManagerRequest::Password(ssid, password, hw_address),
success: false,
state: NetworkManagerState::new(&conn).await.unwrap_or_default(),
})
.await;
} }
_ = output
.send(NetworkManagerEvent::RequestResponse {
req: NetworkManagerRequest::Authenticate {
ssid: ssid.clone(),
identity: identity.clone(),
password: password.clone(),
hw_address,
},
success,
state: NetworkManagerState::new(&conn).await.unwrap_or_default(),
})
.await;
} }
Some(NetworkManagerRequest::SelectAccessPoint(ssid, hw_address)) => { Some(NetworkManagerRequest::SelectAccessPoint(ssid, hw_address, network_type)) => {
// wait for identity before attempting to connect.
if !matches!(network_type, NetworkType::Open) {
return State::Waiting(conn, rx);
}
let state = NetworkManagerState::new(&conn).await.unwrap_or_default(); let state = NetworkManagerState::new(&conn).await.unwrap_or_default();
let success = if let Err(err) =
state.connect_wifi(&conn, &ssid, None, hw_address).await let success = if let Err(err) = state
.connect_wifi(&conn, &ssid, None, None, hw_address)
.await
{ {
tracing::error!("Failed to connect to access point: {:?}", err); tracing::error!("Failed to connect to access point: {:?}", err);
false false
@ -228,7 +242,11 @@ async fn start_listening(
_ = output _ = output
.send(NetworkManagerEvent::RequestResponse { .send(NetworkManagerEvent::RequestResponse {
req: NetworkManagerRequest::SelectAccessPoint(ssid.clone(), hw_address), req: NetworkManagerRequest::SelectAccessPoint(
ssid.clone(),
hw_address,
network_type,
),
success, success,
state: NetworkManagerState::new(&conn).await.unwrap_or_default(), state: NetworkManagerState::new(&conn).await.unwrap_or_default(),
}) })
@ -287,9 +305,14 @@ async fn start_listening(
pub enum NetworkManagerRequest { pub enum NetworkManagerRequest {
SetAirplaneMode(bool), SetAirplaneMode(bool),
SetWiFi(bool), SetWiFi(bool),
SelectAccessPoint(String, HwAddress), SelectAccessPoint(String, HwAddress, NetworkType),
Disconnect(String, HwAddress), Disconnect(String, HwAddress),
Password(String, String, HwAddress), Authenticate {
ssid: String,
identity: Option<String>,
password: String,
hw_address: HwAddress,
},
Forget(String, HwAddress), Forget(String, HwAddress),
Reload, Reload,
} }
@ -415,6 +438,15 @@ impl NetworkManagerState {
.collect(); .collect();
wireless_access_points.sort_by(|a, b| b.strength.cmp(&a.strength)); wireless_access_points.sort_by(|a, b| b.strength.cmp(&a.strength));
self_.wireless_access_points = wireless_access_points; self_.wireless_access_points = wireless_access_points;
for ap in &self_.wireless_access_points {
tracing::info!(
"AP ssid: {},\ttype: {:?},\tworking: {},\tstate: {:?}",
ap.ssid,
ap.network_type,
ap.working,
ap.state
)
}
self_.active_conns = active_conns; self_.active_conns = active_conns;
self_.known_access_points = known_access_points; self_.known_access_points = known_access_points;
self_.connectivity = network_manager.connectivity().await?; self_.connectivity = network_manager.connectivity().await?;
@ -433,6 +465,7 @@ impl NetworkManagerState {
&self, &self,
conn: &Connection, conn: &Connection,
ssid: &str, ssid: &str,
identity: Option<&str>,
password: Option<&str>, password: Option<&str>,
hw_address: HwAddress, hw_address: HwAddress,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
@ -470,7 +503,26 @@ impl NetworkManagerState {
), ),
]); ]);
if let Some(pass) = password { if let Some(identity) = identity {
conn_settings.insert(
"802-1x",
HashMap::from([
("identity", Value::Str(identity.into())),
// most common default
("eap", Value::Array(vec!["peap"].into())),
// most common default
("phase2-auth", Value::Str("mschapv2".into())),
("password", Value::Str(password.unwrap_or("").into())),
]),
);
let wireless = conn_settings.get_mut("802-11-wireless").unwrap();
wireless.insert("security", Value::Str("802-11-wireless-security".into()));
wireless.insert("mode", Value::Str("infrastructure".into()));
conn_settings.insert(
"802-11-wireless-security",
HashMap::from([("key-mgmt", Value::Str("wpa-eap".into()))]),
);
} else if let Some(pass) = password {
conn_settings.insert( conn_settings.insert(
"802-11-wireless-security", "802-11-wireless-security",
HashMap::from([ HashMap::from([
@ -543,30 +595,31 @@ impl NetworkManagerState {
.unwrap(); .unwrap();
ActiveConnection::from(active) ActiveConnection::from(active)
}; };
let mut changes = active_conn.receive_state_changed().await;
_ = tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; _ = tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
let mut state = let mut count = 5;
enums::ActiveConnectionState::from(active_conn.state().await.unwrap_or_default()); loop {
return match state { let state = active_conn.state().await;
ActiveConnectionState::Activating => { if let Ok(enums::ActiveConnectionState::Activated) = state {
if let Ok(Some(s)) = tokio::time::timeout( return Ok(());
Duration::from_secs(20), } else if let Ok(enums::ActiveConnectionState::Deactivated) = state {
active_conn.receive_state_changed().await.next(), anyhow::bail!("Failed to activate connection");
)
.await
{
state = s.get().await.unwrap_or_default().into();
if matches!(state, enums::ActiveConnectionState::Activated) {
Ok(())
} else {
Err(anyhow::anyhow!("Failed to activate connection"))
}
} else {
Err(anyhow::anyhow!("Failed to activate connection"))
}
} }
ActiveConnectionState::Activated => Ok(()), match tokio::time::timeout(Duration::from_secs(20), changes.next()).await {
_ => Err(anyhow::anyhow!("Failed to activate connection")), Ok(Some(s)) => {
}; let state = s.get().await.unwrap_or_default().into();
if matches!(state, enums::ActiveConnectionState::Activated) {
return Ok(());
}
}
_ => {}
};
count -= 1;
if count <= 0 {
anyhow::bail!("Failed to activate connection");
}
}
} }
Err(anyhow::anyhow!("No wifi device found")) Err(anyhow::anyhow!("No wifi device found"))