WIP: configure-switch #159
@ -7,7 +7,7 @@ use std::{
|
|||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use harmony_types::net::{IpAddress, MacAddress};
|
use harmony_types::net::{IpAddress, MacAddress};
|
||||||
use log::{debug, info};
|
use log::{debug, info, trace};
|
||||||
use russh::{
|
use russh::{
|
||||||
ChannelMsg,
|
ChannelMsg,
|
||||||
client::{Handle, Handler},
|
client::{Handle, Handler},
|
||||||
@ -79,6 +79,11 @@ impl Default for SshOptions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum ExecutionMode {
|
||||||
|
Regular,
|
||||||
|
Privileged,
|
||||||
|
}
|
||||||
|
|
||||||
impl BrocadeClient {
|
impl BrocadeClient {
|
||||||
pub async fn init(
|
pub async fn init(
|
||||||
ip_addresses: &[IpAddress],
|
ip_addresses: &[IpAddress],
|
||||||
@ -115,7 +120,9 @@ impl BrocadeClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn show_mac_address_table(&self) -> Result<Vec<MacAddressEntry>, Error> {
|
pub async fn show_mac_address_table(&self) -> Result<Vec<MacAddressEntry>, Error> {
|
||||||
let output = self.run_command("show mac-address").await?;
|
let output = self
|
||||||
|
.run_command("show mac-address", ExecutionMode::Regular)
|
||||||
|
.await?;
|
||||||
|
|
||||||
output
|
output
|
||||||
.lines()
|
.lines()
|
||||||
@ -130,14 +137,15 @@ impl BrocadeClient {
|
|||||||
let channel_id = self.find_available_channel_id().await?;
|
let channel_id = self.find_available_channel_id().await?;
|
||||||
let commands = self.build_port_channel_commands(channel_id, ports);
|
let commands = self.build_port_channel_commands(channel_id, ports);
|
||||||
|
|
||||||
self.run_commands(commands).await?;
|
self.run_commands(commands, ExecutionMode::Privileged)
|
||||||
|
.await?;
|
||||||
Ok(channel_id)
|
Ok(channel_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn find_available_channel_id(&self) -> Result<u8, Error> {
|
pub async fn find_available_channel_id(&self) -> Result<u8, Error> {
|
||||||
debug!("[Brocade] Finding next available channel id...");
|
debug!("[Brocade] Finding next available channel id...");
|
||||||
|
|
||||||
let output = self.run_command("show lag").await?;
|
let output = self.run_command("show lag", ExecutionMode::Regular).await?;
|
||||||
let mut used_ids: Vec<u8> = output
|
let mut used_ids: Vec<u8> = output
|
||||||
.lines()
|
.lines()
|
||||||
.filter_map(|line| {
|
.filter_map(|line| {
|
||||||
@ -158,14 +166,13 @@ impl BrocadeClient {
|
|||||||
Ok(next_id)
|
Ok(next_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run_command(&self, command: &str) -> Result<String, Error> {
|
async fn run_command(&self, command: &str, mode: ExecutionMode) -> Result<String, Error> {
|
||||||
if self.should_skip_command(command) {
|
if self.should_skip_command(command) {
|
||||||
return Ok(String::new());
|
return Ok(String::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut channel = self.client.channel_open_session().await?;
|
let mut channel = self.client.channel_open_session().await?;
|
||||||
self.setup_channel(&mut channel).await?;
|
self.setup_channel(&mut channel, mode).await?;
|
||||||
self.wait_for_shell_ready(&mut channel).await?;
|
|
||||||
|
|
||||||
let output = self
|
let output = self
|
||||||
.execute_command_in_session(&mut channel, command)
|
.execute_command_in_session(&mut channel, command)
|
||||||
@ -178,14 +185,13 @@ impl BrocadeClient {
|
|||||||
Ok(cleaned)
|
Ok(cleaned)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run_commands(&self, commands: Vec<String>) -> Result<(), Error> {
|
async fn run_commands(&self, commands: Vec<String>, mode: ExecutionMode) -> Result<(), Error> {
|
||||||
if commands.is_empty() {
|
if commands.is_empty() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut channel = self.client.channel_open_session().await?;
|
let mut channel = self.client.channel_open_session().await?;
|
||||||
self.setup_channel(&mut channel).await?;
|
self.setup_channel(&mut channel, mode).await?;
|
||||||
self.wait_for_shell_ready(&mut channel).await?;
|
|
||||||
|
|
||||||
for command in commands {
|
for command in commands {
|
||||||
if self.should_skip_command(&command) {
|
if self.should_skip_command(&command) {
|
||||||
@ -210,13 +216,23 @@ impl BrocadeClient {
|
|||||||
async fn setup_channel(
|
async fn setup_channel(
|
||||||
&self,
|
&self,
|
||||||
channel: &mut russh::Channel<russh::client::Msg>,
|
channel: &mut russh::Channel<russh::client::Msg>,
|
||||||
|
mode: ExecutionMode,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
// Setup PTY and shell
|
// Setup PTY and shell
|
||||||
channel
|
channel
|
||||||
.request_pty(false, "vt100", 80, 24, 0, 0, &[])
|
.request_pty(false, "vt100", 80, 24, 0, 0, &[])
|
||||||
.await?;
|
.await?;
|
||||||
channel.request_shell(false).await?;
|
channel.request_shell(false).await?;
|
||||||
Ok(())
|
|
||||||
|
self.wait_for_shell_ready(channel).await?;
|
||||||
|
|
||||||
|
match mode {
|
||||||
|
ExecutionMode::Regular => Ok(()),
|
||||||
|
ExecutionMode::Privileged => {
|
||||||
|
debug!("[Brocade] Attempting privilege escalation (enable mode)...");
|
||||||
|
self.try_elevate_session(channel).await
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn execute_command_in_session(
|
async fn execute_command_in_session(
|
||||||
@ -235,6 +251,63 @@ impl BrocadeClient {
|
|||||||
.map_err(|_| Error::UnexpectedError("Invalid UTF-8 in command output".to_string()))
|
.map_err(|_| Error::UnexpectedError("Invalid UTF-8 in command output".to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn try_elevate_session(
|
||||||
|
&self,
|
||||||
|
channel: &mut russh::Channel<russh::client::Msg>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
channel.data(&b"enable\n"[..]).await?;
|
||||||
|
let start = Instant::now();
|
||||||
|
let mut buffer = Vec::new();
|
||||||
|
|
||||||
|
while start.elapsed() < self.options.timeouts.shell_ready {
|
||||||
|
match timeout(self.options.timeouts.message_wait, channel.wait()).await {
|
||||||
|
Ok(Some(ChannelMsg::Data { data })) => {
|
||||||
|
buffer.extend_from_slice(&data);
|
||||||
|
let output = String::from_utf8_lossy(&buffer);
|
||||||
|
|
||||||
|
if output.ends_with('#') {
|
||||||
|
debug!("[Brocade] Privileged mode established");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
if output.contains("User Name:") {
|
||||||
|
channel
|
||||||
|
.data(format!("{}\n", self.elevated_user.username).as_bytes())
|
||||||
|
.await?;
|
||||||
|
buffer.clear();
|
||||||
|
} else if output.contains("Password:") {
|
||||||
|
// Note: Brocade might not echo the password field
|
||||||
|
channel
|
||||||
|
.data(format!("{}\n", self.elevated_user.password).as_bytes())
|
||||||
|
.await?;
|
||||||
|
buffer.clear();
|
||||||
|
} else if output.contains('>') {
|
||||||
|
// Back to user mode, something failed (e.g., wrong password)
|
||||||
|
return Err(Error::AuthenticationError(
|
||||||
|
"Enable authentication failed or access denied.".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Some(_)) => continue,
|
||||||
|
Ok(None) => break,
|
||||||
|
Err(_) => continue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check final state if timeout was reached
|
||||||
|
let output = String::from_utf8_lossy(&buffer);
|
||||||
|
let elevated = output.ends_with('#');
|
||||||
|
match elevated {
|
||||||
|
true => {
|
||||||
|
debug!("[Brocade] Privileged mode established");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
false => Err(Error::AuthenticationError(format!(
|
||||||
|
"Enable authentication failed for an unknown reason. Output was:\n{output}",
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn wait_for_shell_ready(
|
async fn wait_for_shell_ready(
|
||||||
&self,
|
&self,
|
||||||
channel: &mut russh::Channel<russh::client::Msg>,
|
channel: &mut russh::Channel<russh::client::Msg>,
|
||||||
@ -247,7 +320,7 @@ impl BrocadeClient {
|
|||||||
Ok(Some(ChannelMsg::Data { data })) => {
|
Ok(Some(ChannelMsg::Data { data })) => {
|
||||||
buffer.extend_from_slice(&data);
|
buffer.extend_from_slice(&data);
|
||||||
let output = String::from_utf8_lossy(&buffer);
|
let output = String::from_utf8_lossy(&buffer);
|
||||||
if output.contains('>') || output.contains('#') {
|
if output.ends_with('>') || output.ends_with('#') {
|
||||||
debug!("[Brocade] Shell ready: {}", output.trim());
|
debug!("[Brocade] Shell ready: {}", output.trim());
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@ -351,7 +424,7 @@ impl BrocadeClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn clean_brocade_output(&self, raw_output: &str, command: &str) -> String {
|
fn clean_brocade_output(&self, raw_output: &str, command: &str) -> String {
|
||||||
debug!("[Brocade] Received command output:\n{raw_output}");
|
trace!("[Brocade] Received raw output:\n{raw_output}");
|
||||||
|
|
||||||
let lines: Vec<&str> = raw_output.lines().collect();
|
let lines: Vec<&str> = raw_output.lines().collect();
|
||||||
let mut cleaned_lines = Vec::new();
|
let mut cleaned_lines = Vec::new();
|
||||||
|
Loading…
Reference in New Issue
Block a user