Reconnection
This commit is contained in:
parent
6b351ec9d2
commit
e07ea9ea7e
3 changed files with 121 additions and 8 deletions
|
|
@ -2,7 +2,8 @@ use crossterm::event::{KeyCode, KeyEvent};
|
||||||
use iota_logger::{log, log_command, log_cv};
|
use iota_logger::{log, log_command, log_cv};
|
||||||
use iota_state::{ACTIVE_TASKS, RELOAD, SHUTDOWN};
|
use iota_state::{ACTIVE_TASKS, RELOAD, SHUTDOWN};
|
||||||
use iota_storage::users::{user_manager, user_profile::UserProfile};
|
use iota_storage::users::{user_manager, user_profile::UserProfile};
|
||||||
use iota_util::file_util;
|
use iota_storage::util::config_util::CONFIG;
|
||||||
|
use iota_util::{crypto_helper, file_util};
|
||||||
use omikron_connector::omikron_connection::OMIKRON_CONNECTION;
|
use omikron_connector::omikron_connection::OMIKRON_CONNECTION;
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
Frame,
|
Frame,
|
||||||
|
|
@ -400,7 +401,7 @@ pub async fn run_command(command: &str) {
|
||||||
}
|
}
|
||||||
|
|
||||||
["help"] => {
|
["help"] => {
|
||||||
log!("Available commands: tasks, fps, ping, user");
|
log!("Available commands: tasks, fps, ping, user, reconnect, regenerate");
|
||||||
}
|
}
|
||||||
|
|
||||||
["help", "tasks"] => {
|
["help", "tasks"] => {
|
||||||
|
|
@ -415,6 +416,12 @@ pub async fn run_command(command: &str) {
|
||||||
["help", "user"] => {
|
["help", "user"] => {
|
||||||
log!("User command usage: user add <username> | user remove <username> | user list");
|
log!("User command usage: user add <username> | user remove <username> | user list");
|
||||||
}
|
}
|
||||||
|
["help", "reconnect"] => {
|
||||||
|
log!("Reconnect command usage: reconnect — retry connecting to the Omikron server");
|
||||||
|
}
|
||||||
|
["help", "regenerate"] => {
|
||||||
|
log!("Regenerate command usage: regenerate private-key — generate a new Iota key pair and reconnect");
|
||||||
|
}
|
||||||
|
|
||||||
["ping"] => {
|
["ping"] => {
|
||||||
ping(20).await;
|
ping(20).await;
|
||||||
|
|
@ -460,6 +467,26 @@ pub async fn run_command(command: &str) {
|
||||||
log!("User info: Username doesn't exist");
|
log!("User info: Username doesn't exist");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
["reconnect"] => {
|
||||||
|
log!("Reconnecting to Omikron server...");
|
||||||
|
OMIKRON_CONNECTION.reconnect().await;
|
||||||
|
log!("Reconnected to Omikron server");
|
||||||
|
}
|
||||||
|
["regenerate", "private-key"] => {
|
||||||
|
log!("Regenerating Iota key pair...");
|
||||||
|
let key_pair = crypto_helper::generate_keypair();
|
||||||
|
let public_key_base64 = crypto_helper::public_key_to_base64(&key_pair.public);
|
||||||
|
let private_key_base64 = crypto_helper::secret_key_to_base64(&key_pair.secret);
|
||||||
|
{
|
||||||
|
let mut conf = CONFIG.write().await;
|
||||||
|
conf.change("public_key", json::JsonValue::from(public_key_base64));
|
||||||
|
conf.change("private_key", json::JsonValue::from(private_key_base64));
|
||||||
|
conf.update();
|
||||||
|
}
|
||||||
|
log!("Key pair regenerated. Reconnecting to Omikron server...");
|
||||||
|
OMIKRON_CONNECTION.reconnect().await;
|
||||||
|
log!("Reconnected with new key pair");
|
||||||
|
}
|
||||||
["reload"] | ["restart"] => {
|
["reload"] | ["restart"] => {
|
||||||
log!("Restarting");
|
log!("Restarting");
|
||||||
*RELOAD.write().await = true;
|
*RELOAD.write().await = true;
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ use iota_storage::users::user_manager;
|
||||||
use iota_storage::util::config_util::CONFIG;
|
use iota_storage::util::config_util::CONFIG;
|
||||||
use iota_util::file_util::{download_and_extract_zip, has_dir};
|
use iota_util::file_util::{download_and_extract_zip, has_dir};
|
||||||
use omikron_connector as omikron;
|
use omikron_connector as omikron;
|
||||||
use ttp_core::{CommunicationType, DataTypes};
|
use omikron_connector::omikron_connection::OMIKRON_CONNECTION;
|
||||||
|
|
||||||
#[tokio::main(flavor = "multi_thread", worker_threads = 16)]
|
#[tokio::main(flavor = "multi_thread", worker_threads = 16)]
|
||||||
#[allow(unused_must_use, dead_code, unused_assignments)]
|
#[allow(unused_must_use, dead_code, unused_assignments)]
|
||||||
|
|
@ -159,7 +159,15 @@ async fn main() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
sleep(Duration::from_millis(100)).await;
|
if OMIKRON_CONNECTION.has_auth_failure().await {
|
||||||
|
if let Some(reason) = OMIKRON_CONNECTION.get_auth_failure().await {
|
||||||
|
log!("Authentication failed: {}", reason);
|
||||||
|
log!("Use /reconnect to try again or /regenerate private-key to create a new key pair");
|
||||||
|
OMIKRON_CONNECTION.clear_auth_failure().await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sleep(Duration::from_millis(500)).await;
|
||||||
}
|
}
|
||||||
if *RELOAD.read().await {
|
if *RELOAD.read().await {
|
||||||
loop {
|
loop {
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,7 @@ pub struct OmikronConnection {
|
||||||
pub connection_id: Uuid,
|
pub connection_id: Uuid,
|
||||||
shutdown_tx: Arc<Mutex<Option<watch::Sender<bool>>>>,
|
shutdown_tx: Arc<Mutex<Option<watch::Sender<bool>>>>,
|
||||||
reconnect_on_close: Arc<RwLock<bool>>,
|
reconnect_on_close: Arc<RwLock<bool>>,
|
||||||
|
auth_failure: Arc<RwLock<Option<String>>>,
|
||||||
pub app_challenges: Arc<RwLock<HashMap<u64, String>>>,
|
pub app_challenges: Arc<RwLock<HashMap<u64, String>>>,
|
||||||
pub app_sessions: Arc<RwLock<HashMap<u64, (i64, String)>>>,
|
pub app_sessions: Arc<RwLock<HashMap<u64, (i64, String)>>>,
|
||||||
}
|
}
|
||||||
|
|
@ -114,6 +115,7 @@ impl OmikronConnection {
|
||||||
connection_id: Uuid::new_v4(),
|
connection_id: Uuid::new_v4(),
|
||||||
shutdown_tx: Arc::new(Mutex::new(Some(shutdown_tx))),
|
shutdown_tx: Arc::new(Mutex::new(Some(shutdown_tx))),
|
||||||
reconnect_on_close: Arc::new(RwLock::new(true)),
|
reconnect_on_close: Arc::new(RwLock::new(true)),
|
||||||
|
auth_failure: Arc::new(RwLock::new(None)),
|
||||||
app_challenges: Arc::new(RwLock::new(HashMap::new())),
|
app_challenges: Arc::new(RwLock::new(HashMap::new())),
|
||||||
app_sessions: Arc::new(RwLock::new(HashMap::new())),
|
app_sessions: Arc::new(RwLock::new(HashMap::new())),
|
||||||
}
|
}
|
||||||
|
|
@ -134,6 +136,11 @@ impl OmikronConnection {
|
||||||
handle.abort();
|
handle.abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.shutdown_tx.lock().await.is_none() {
|
||||||
|
let (shutdown_tx, _) = watch::channel(false);
|
||||||
|
*self.shutdown_tx.lock().await = Some(shutdown_tx);
|
||||||
|
}
|
||||||
|
|
||||||
*self.reconnect_on_close.write().await = true;
|
*self.reconnect_on_close.write().await = true;
|
||||||
|
|
||||||
let self_clone = self.clone();
|
let self_clone = self.clone();
|
||||||
|
|
@ -191,6 +198,10 @@ impl OmikronConnection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
if self.auth_failure.read().await.is_some() {
|
||||||
|
log!("Authentication failed, stopping reconnection: {}", e);
|
||||||
|
break;
|
||||||
|
}
|
||||||
log!(
|
log!(
|
||||||
"Connection failed: {}, retrying in {:?}...",
|
"Connection failed: {}, retrying in {:?}...",
|
||||||
e,
|
e,
|
||||||
|
|
@ -256,6 +267,22 @@ impl OmikronConnection {
|
||||||
// Handle registration/identification
|
// Handle registration/identification
|
||||||
self.handle_authentication().await;
|
self.handle_authentication().await;
|
||||||
|
|
||||||
|
// Wait for identification to complete
|
||||||
|
if !self.await_identification(Duration::from_secs(30)).await {
|
||||||
|
*self.reconnect_on_close.write().await = false;
|
||||||
|
let reason = "Authentication failed: server did not accept the challenge. Your Iota keys may be invalid or the private key has changed on the server."
|
||||||
|
.to_string();
|
||||||
|
*self.auth_failure.write().await = Some(reason.clone());
|
||||||
|
|
||||||
|
if let Some(sender) = self.sender.write().await.take() {
|
||||||
|
sender.close();
|
||||||
|
}
|
||||||
|
*self.state.write().await = ConnectionState::Disconnected;
|
||||||
|
return Err(reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
log_t!("omikron_authenticated");
|
||||||
|
|
||||||
// Start heartbeat
|
// Start heartbeat
|
||||||
let heartbeat_self = self.clone();
|
let heartbeat_self = self.clone();
|
||||||
let heartbeat_handle = tokio::spawn(async move {
|
let heartbeat_handle = tokio::spawn(async move {
|
||||||
|
|
@ -696,12 +723,22 @@ impl OmikronConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
if cv.is_type(CommunicationType::identification_response) {
|
if cv.is_type(CommunicationType::identification_response) {
|
||||||
if let Some(_accepted) = cv.get_data(DataTypes::accepted).as_bool() {
|
match cv.get_data(DataTypes::accepted).as_bool() {
|
||||||
|
Some(true) => {
|
||||||
let mut state = self.state.write().await;
|
let mut state = self.state.write().await;
|
||||||
if let ConnectionState::Connected { identified: _ } = *state {
|
if let ConnectionState::Connected { identified: _ } = *state {
|
||||||
*state = ConnectionState::Connected { identified: true };
|
*state = ConnectionState::Connected { identified: true };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some(false) => {
|
||||||
|
*self.auth_failure.write().await = Some(
|
||||||
|
"Server rejected the challenge response — your Iota keys may be invalid."
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
log_t!("omikron_auth_rejected");
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1360,6 +1397,28 @@ impl OmikronConnection {
|
||||||
.add_data(DataTypes::challenge, DataValue::Str(solved));
|
.add_data(DataTypes::challenge, DataValue::Str(solved));
|
||||||
|
|
||||||
self.send_message(&response).await;
|
self.send_message(&response).await;
|
||||||
|
} else {
|
||||||
|
log_t!("omikron_challenge_decryption_failed");
|
||||||
|
*self.auth_failure.write().await = Some(
|
||||||
|
"Challenge decryption failed — your Iota private key may not match the registered key on the server."
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn await_identification(&self, timeout: Duration) -> bool {
|
||||||
|
let start = Instant::now();
|
||||||
|
loop {
|
||||||
|
if self.state.read().await.is_identified() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if self.auth_failure.read().await.is_some() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if start.elapsed() >= timeout {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
sleep(Duration::from_millis(100)).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1521,6 +1580,25 @@ impl OmikronConnection {
|
||||||
sleep(Duration::from_millis(100)).await;
|
sleep(Duration::from_millis(100)).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn has_auth_failure(&self) -> bool {
|
||||||
|
self.auth_failure.read().await.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_auth_failure(&self) -> Option<String> {
|
||||||
|
self.auth_failure.read().await.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn clear_auth_failure(&self) {
|
||||||
|
*self.auth_failure.write().await = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn reconnect(self: &Arc<Self>) {
|
||||||
|
self.clear_auth_failure().await;
|
||||||
|
*self.reconnect_on_close.write().await = true;
|
||||||
|
self.stop().await;
|
||||||
|
self.connect().await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue