Skip to content

Commit

Permalink
Command line interface
Browse files Browse the repository at this point in the history
  • Loading branch information
damian-tworek committed Aug 12, 2023
1 parent decc567 commit 9b64e17
Show file tree
Hide file tree
Showing 8 changed files with 478 additions and 1,200 deletions.
1,391 changes: 296 additions & 1,095 deletions Cargo.lock

Large diffs are not rendered by default.

6 changes: 1 addition & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,6 @@ etherparse = "0.13.0"
rtp = "0.6.8"
rtcp = "0.7.2"
webrtc-util = "0.7.0"
egui = "0.22.0"
eframe = { version = "0.22.0", default-features = false, features = [
"default_fonts",
"glow"
] }
rfd = "0.11.3"

# not using workspaces, as the crates use different targets
3 changes: 3 additions & 0 deletions src/command_line_interface.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
mod device_selector;
mod file_selector;
pub mod start_interface;
104 changes: 104 additions & 0 deletions src/command_line_interface/device_selector.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
use pcap::{ConnectionStatus, Device, IfFlags};
use std::io::stdin;
use std::net::IpAddr;
use std::thread::sleep;
use std::time::Duration;

pub(crate) fn select_device() -> String {
loop {
list_devices();
println!("Enter the name of the chosen device:");
let mut chosen_name = String::new();
stdin()
.read_line(&mut chosen_name)
.expect("Failed to read line");
let chosen_name = chosen_name.trim().to_string();

if Device::list()
.expect("Error listing devices")
.iter()
.any(|dev| dev.name == chosen_name)
{
return chosen_name;
} else {
println!("\nError: Invalid device name. Please choose a valid device.\n");
sleep(Duration::new(1, 0))
}
}
}

fn list_devices() {
println!("Available network devices:");
let devices = Device::list().expect("Error listing devices");
for device in devices {
println!("Name: {}", device.name);
println!("Description: {}", device.desc.unwrap_or("N/A".to_string()));
println!(
"Addresses: {}",
if device.addresses.is_empty() {
"N/A"
} else {
""
}
);
for address in &device.addresses {
println!(" Address: {}", format_ip_addr(&address.addr));
println!(" Netmask: {}", format_optional_ip(&address.netmask));
println!(
" Broadcast: {}",
format_optional_ip(&address.broadcast_addr)
);
println!(" Destination: {}\n", format_optional_ip(&address.dst_addr));
}

println!("Flags: {}", format_flags(device.flags.if_flags));
println!(
"Connection status: {}",
format_connection_status(device.flags.connection_status)
);
println!("--------------------------------------\n");
}
}

fn format_flags(flags: IfFlags) -> String {
let mut result = Vec::new();

if flags.contains(IfFlags::LOOPBACK) {
result.push("LOOPBACK");
}
if flags.contains(IfFlags::UP) {
result.push("UP");
}
if flags.contains(IfFlags::RUNNING) {
result.push("RUNNING");
}
if flags.contains(IfFlags::WIRELESS) {
result.push("WIRELESS");
}

if result.is_empty() {
result.push("N/A")
}
result.join(" | ")
}

fn format_connection_status(status: ConnectionStatus) -> String {
match status {
ConnectionStatus::Unknown => "Unknown".to_string(),
ConnectionStatus::Connected => "Connected".to_string(),
ConnectionStatus::Disconnected => "Disconnected".to_string(),
ConnectionStatus::NotApplicable => "N/A".to_string(),
}
}

fn format_ip_addr(ip: &IpAddr) -> String {
match ip {
IpAddr::V4(ipv4) => ipv4.to_string(),
IpAddr::V6(ipv6) => ipv6.to_string(),
}
}

fn format_optional_ip(ip: &Option<IpAddr>) -> String {
ip.as_ref()
.map_or("None".to_string(), |addr| format_ip_addr(addr))
}
13 changes: 13 additions & 0 deletions src/command_line_interface/file_selector.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
pub(crate) fn show_pick_file_dialog() -> String {
loop {
if let Some(path_buf) = rfd::FileDialog::new().pick_file() {
if let Some(file_path) = path_buf.to_str() {
return file_path.to_string();
} else {
println!("Invalid file path. Please try again.");
}
} else {
println!("Error showing file dialog. Please try again.");
}
}
}
41 changes: 41 additions & 0 deletions src/command_line_interface/start_interface.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use crate::command_line_interface::device_selector::select_device;
use crate::command_line_interface::file_selector::show_pick_file_dialog;
use crate::command_line_interface::start_interface::Action::{AnalyzeFile, CapturePackets};
use std::io::stdin;

pub enum Action {
CapturePackets(String),
AnalyzeFile(String),
}

pub fn command_line_interface() -> Action {
let selected_action;
loop {
println!("Welcome to the rtpeeker!");
println!("Please choose an option:");
println!("A) Capture packets from a network interface");
println!("B) Analyze packets from a file");

let choice = read_line();

if choice == "A" {
let device = select_device();
selected_action = CapturePackets(device);
break;
} else if choice == "B" {
let path = show_pick_file_dialog();
selected_action = AnalyzeFile(path);
break;
} else {
println!("Invalid choice.\n");
}
}
selected_action
}

fn read_line() -> String {
let mut choice = String::new();
stdin().read_line(&mut choice).expect("Failed to read line");
let choice = choice.trim().to_uppercase();
choice
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pub mod command_line_interface;
pub mod sniffer;
119 changes: 19 additions & 100 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
use std::io;
use pcap::{Device, ConnectionStatus, IfFlags};
use std::net::IpAddr;


use rtpeeker::command_line_interface::start_interface::command_line_interface;
use rtpeeker::command_line_interface::start_interface::Action::{AnalyzeFile, CapturePackets};
use rtpeeker::sniffer;

// #[tokio::main]
// async fn main() {
Expand All @@ -11,32 +9,17 @@ use std::net::IpAddr;
// .await;
// }

use rtpeeker::sniffer;

#[tokio::main]
async fn main() {
let devices = Device::list().expect("Error listing devices");

fn main() -> Result<(), eframe::Error> {
let options = eframe::NativeOptions {
initial_window_size: Some(egui::vec2(1600.0, 640.0)),
..Default::default()
};
eframe::run_native(
"Media Stream Analyzer",
options,
Box::new(|_cc| Box::new(ViewState::new())),
)
}

list_devices(devices);
let device = select_device();
// TODO first choose file (open dialog)
// TODO loop on error


let action = command_line_interface();
match action {
CapturePackets(device) => capture_packets(device),
AnalyzeFile(path) => analyze_file(path),
};
}

let Ok(mut sniffer) = sniffer::Sniffer::from_device(device.as_str()) else {
fn analyze_file(path: String) {
let Ok(mut sniffer) = sniffer::Sniffer::from_file(path.as_str()) else {
println!("Cannot open file");
return;
};
Expand All @@ -45,80 +28,16 @@ async fn main() {
packet.parse_as(sniffer::packet::PacketType::RtpOverUdp);
println!("{:?}", packet);
}


// let Ok(mut sniffer) = sniffer::Sniffer::from_file("./pcap_examples/rtp.pcap") else {
// println!("Cannot open file");
// return;
// };
//
}

fn capture_packets(device: String) {
let Ok(mut sniffer) = sniffer::Sniffer::from_device(device.as_str()) else {
println!("Cannot open network interface");
return;
};

fn format_flags(flags: IfFlags) -> String {
let mut result = Vec::new();

if flags.contains(IfFlags::LOOPBACK) {
result.push("LOOPBACK");
}
if flags.contains(IfFlags::UP) {
result.push("UP");
}
if flags.contains(IfFlags::RUNNING) {
result.push("RUNNING");
}
if flags.contains(IfFlags::WIRELESS) {
result.push("WIRELESS");
}

if result.is_empty() {
result.push("N/A")
}
result.join(" | ")
}

fn format_connection_status(status: ConnectionStatus) -> String {
match status {
ConnectionStatus::Unknown => "Unknown".to_string(),
ConnectionStatus::Connected => "Connected".to_string(),
ConnectionStatus::Disconnected => "Disconnected".to_string(),
ConnectionStatus::NotApplicable => "N/A".to_string(),
}
}

fn format_ip_addr(ip: &IpAddr) -> String {
match ip {
IpAddr::V4(ipv4) => ipv4.to_string(),
IpAddr::V6(ipv6) => ipv6.to_string(),
}
}

fn format_optional_ip(ip: &Option<IpAddr>) -> String {
ip.as_ref().map_or("None".to_string(), |addr| format_ip_addr(addr))
}

fn select_device() -> String {
println!("Enter the name of the chosen device:");
let mut chosen_name = String::new();
io::stdin().read_line(&mut chosen_name).expect("Failed to read line");
chosen_name.trim().to_string()
}

fn list_devices(devices: Vec<Device>) {
println!("Available network devices:");
for device in devices {
println!("Name: {}", device.name);
println!("Description: {}", device.desc.unwrap_or("N/A".to_string()));
println!("Addresses: {}", if device.addresses.is_empty() { "N/A" } else { "" });
for address in &device.addresses {
println!(" Address: {}", format_ip_addr(&address.addr));
println!(" Netmask: {}", format_optional_ip(&address.netmask));
println!(" Broadcast: {}", format_optional_ip(&address.broadcast_addr));
println!(" Destination: {}\n", format_optional_ip(&address.dst_addr));
}

println!("Flags: {}", format_flags(device.flags.if_flags));
println!("Connection status: {}", format_connection_status(device.flags.connection_status));
println!("\n");
while let Ok(mut packet) = sniffer.next_packet() {
packet.parse_as(sniffer::packet::PacketType::RtpOverUdp);
println!("{:?}", packet);
}
}

0 comments on commit 9b64e17

Please sign in to comment.