Skip to content

Commit

Permalink
feat: added ICMP extensions support to frontend (WIP)
Browse files Browse the repository at this point in the history
  • Loading branch information
fujiapple852 committed Nov 26, 2023
1 parent 3329218 commit ef3a64a
Show file tree
Hide file tree
Showing 9 changed files with 196 additions and 29 deletions.
29 changes: 29 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,25 @@ pub enum AsMode {
Name,
}

/// How to render `icmp` extensions in the hops table.
#[derive(Debug, Copy, Clone, Eq, PartialEq, ValueEnum, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum IcmpExtensionMode {
/// Do not show `icmp` extensions.
Off,
/// Show MPLS label(s) only.
Mpls,
/// Show full `icmp` extension data for all known extensions.
///
/// For MPLS the fields shown are `label`, `ttl`, `exp` & `bos`.
Full,
/// Show `icmp` extension data for all classes.
///
/// This is the same as `Full`, but also shows `class`, `subtype` and
/// `object` for unknown extensions.
All,
}

/// How to render `GeoIp` information in the hop table.
///
/// Note that the hop details view is always shown using the `Long` representation.
Expand Down Expand Up @@ -217,6 +236,7 @@ pub struct TrippyConfig {
pub tui_privacy_max_ttl: u8,
pub tui_address_mode: AddressMode,
pub tui_as_mode: AsMode,
pub tui_icmp_extension_mode: IcmpExtensionMode,
pub tui_geoip_mode: GeoIpMode,
pub tui_max_addrs: Option<u8>,
pub tui_theme: TuiTheme,
Expand Down Expand Up @@ -418,6 +438,13 @@ impl TrippyConfig {
cfg_file_tui.tui_as_mode,
constants::DEFAULT_TUI_AS_MODE,
);

let tui_icmp_extension_mode = cfg_layer(
args.tui_icmp_extension_mode,
cfg_file_tui.tui_icmp_extension_mode,
constants::DEFAULT_TUI_ICMP_EXTENSION_MODE,
);

let tui_geoip_mode = cfg_layer(
args.tui_geoip_mode,
cfg_file_tui.tui_geoip_mode,
Expand Down Expand Up @@ -577,6 +604,7 @@ impl TrippyConfig {
tui_privacy_max_ttl,
tui_address_mode,
tui_as_mode,
tui_icmp_extension_mode,
tui_geoip_mode,
tui_max_addrs,
tui_theme,
Expand Down Expand Up @@ -626,6 +654,7 @@ impl Default for TrippyConfig {
tui_privacy_max_ttl: constants::DEFAULT_TUI_PRIVACY_MAX_TTL,
tui_address_mode: constants::DEFAULT_TUI_ADDRESS_MODE,
tui_as_mode: constants::DEFAULT_TUI_AS_MODE,
tui_icmp_extension_mode: constants::DEFAULT_TUI_ICMP_EXTENSION_MODE,
tui_geoip_mode: constants::DEFAULT_TUI_GEOIP_MODE,
tui_max_addrs: None,
tui_theme: TuiTheme::default(),
Expand Down
8 changes: 6 additions & 2 deletions src/config/cmd.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::config::binding::TuiCommandItem;
use crate::config::theme::TuiThemeItem;
use crate::config::{
AddressMode, AsMode, DnsResolveMethodConfig, GeoIpMode, LogFormat, LogSpanEvents, Mode,
MultipathStrategyConfig, Protocol, TuiColor, TuiKeyBinding,
AddressMode, AsMode, DnsResolveMethodConfig, GeoIpMode, IcmpExtensionMode, LogFormat,
LogSpanEvents, Mode, MultipathStrategyConfig, Protocol, TuiColor, TuiKeyBinding,
};
use anyhow::anyhow;
use clap::builder::Styles;
Expand Down Expand Up @@ -161,6 +161,10 @@ pub struct Args {
#[arg(value_enum, long)]
pub tui_as_mode: Option<AsMode>,

/// How to render ICMP extensions [default: mpls]
#[arg(value_enum, long)]
pub tui_icmp_extension_mode: Option<IcmpExtensionMode>,

/// How to render GeoIp information [default: short]
#[arg(value_enum, long)]
pub tui_geoip_mode: Option<GeoIpMode>,
Expand Down
7 changes: 5 additions & 2 deletions src/config/constants.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::config::{
AddressFamily, AddressMode, AsMode, DnsResolveMethodConfig, GeoIpMode, LogFormat,
LogSpanEvents, Mode, MultipathStrategyConfig, Protocol,
AddressFamily, AddressMode, AsMode, DnsResolveMethodConfig, GeoIpMode, IcmpExtensionMode,
LogFormat, LogSpanEvents, Mode, MultipathStrategyConfig, Protocol,
};
use std::time::Duration;

Expand Down Expand Up @@ -82,6 +82,9 @@ pub const DEFAULT_TUI_PRESERVE_SCREEN: bool = false;
/// The default value for `tui-as-mode`.
pub const DEFAULT_TUI_AS_MODE: AsMode = AsMode::Asn;

/// The default value for `tui-icmp-extension-mode`.
pub const DEFAULT_TUI_ICMP_EXTENSION_MODE: IcmpExtensionMode = IcmpExtensionMode::Mpls;

/// The default value for `tui-geoip-mode`.
pub const DEFAULT_TUI_GEOIP_MODE: GeoIpMode = GeoIpMode::Off;

Expand Down
6 changes: 4 additions & 2 deletions src/config/file.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::config::binding::TuiKeyBinding;
use crate::config::theme::TuiColor;
use crate::config::{
AddressFamily, AddressMode, AsMode, DnsResolveMethodConfig, GeoIpMode, LogFormat,
LogSpanEvents, Mode, MultipathStrategyConfig, Protocol,
AddressFamily, AddressMode, AsMode, DnsResolveMethodConfig, GeoIpMode, IcmpExtensionMode,
LogFormat, LogSpanEvents, Mode, MultipathStrategyConfig, Protocol,
};
use anyhow::Context;
use etcetera::BaseStrategy;
Expand Down Expand Up @@ -218,6 +218,7 @@ pub struct ConfigTui {
pub tui_privacy_max_ttl: Option<u8>,
pub tui_address_mode: Option<AddressMode>,
pub tui_as_mode: Option<AsMode>,
pub tui_icmp_extension_mode: Option<IcmpExtensionMode>,
pub tui_geoip_mode: Option<GeoIpMode>,
pub tui_max_addrs: Option<u8>,
pub geoip_mmdb_file: Option<String>,
Expand All @@ -232,6 +233,7 @@ impl Default for ConfigTui {
tui_privacy_max_ttl: Some(super::constants::DEFAULT_TUI_PRIVACY_MAX_TTL),
tui_address_mode: Some(super::constants::DEFAULT_TUI_ADDRESS_MODE),
tui_as_mode: Some(super::constants::DEFAULT_TUI_AS_MODE),
tui_icmp_extension_mode: Some(super::constants::DEFAULT_TUI_ICMP_EXTENSION_MODE),
tui_geoip_mode: Some(super::constants::DEFAULT_TUI_GEOIP_MODE),
tui_max_addrs: Some(super::constants::DEFAULT_TUI_MAX_ADDRS),
geoip_mmdb_file: None,
Expand Down
6 changes: 5 additions & 1 deletion src/frontend/config.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::config::TuiBindings;
use crate::config::{AddressMode, AsMode, GeoIpMode, TuiTheme};
use crate::config::{IcmpExtensionMode, TuiBindings};
use crate::frontend::binding::Bindings;
use crate::frontend::theme::Theme;
use std::time::Duration;
Expand All @@ -19,6 +19,8 @@ pub struct TuiConfig {
pub lookup_as_info: bool,
/// How to render AS data.
pub as_mode: AsMode,
/// How to render ICMP extensions.
pub icmp_extension_mode: IcmpExtensionMode,
/// How to render GeoIp data.
pub geoip_mode: GeoIpMode,
/// The maximum number of addresses to show per hop.
Expand All @@ -40,6 +42,7 @@ impl TuiConfig {
address_mode: AddressMode,
lookup_as_info: bool,
as_mode: AsMode,
icmp_extension_mode: IcmpExtensionMode,
geoip_mode: GeoIpMode,
max_addrs: Option<u8>,
max_samples: usize,
Expand All @@ -53,6 +56,7 @@ impl TuiConfig {
address_mode,
lookup_as_info,
as_mode,
icmp_extension_mode,
geoip_mode,
max_addrs,
max_samples,
Expand Down
3 changes: 2 additions & 1 deletion src/frontend/render/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ fn format_trace_settings(app: &TuiApp) -> Vec<SettingsItem> {
),
SettingsItem::new("packet-size", format!("{}", cfg.packet_size)),
SettingsItem::new("payload-pattern", format!("{}", cfg.payload_pattern)),
SettingsItem::new("icmp-extensions", format!("{}", cfg.icmp_extensions)),
SettingsItem::new("interface", interface),
SettingsItem::new("multipath-strategy", cfg.multipath_strategy.to_string()),
SettingsItem::new("target-port", dst_port),
Expand Down Expand Up @@ -419,7 +420,7 @@ fn format_theme_settings(app: &TuiApp) -> Vec<SettingsItem> {
/// The name and number of items for each tabs in the setting dialog.
pub const SETTINGS_TABS: [(&str, usize); 6] = [
("Tui", 8),
("Trace", 14),
("Trace", 15),
("Dns", 4),
("GeoIp", 1),
("Bindings", 29),
Expand Down
153 changes: 132 additions & 21 deletions src/frontend/render/table.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::backend::trace::Hop;
use crate::config::{AddressMode, AsMode, GeoIpMode};
use crate::config::{AddressMode, AsMode, GeoIpMode, IcmpExtensionMode};
use crate::frontend::config::TuiConfig;
use crate::frontend::theme::Theme;
use crate::frontend::tui_app::TuiApp;
Expand All @@ -12,6 +12,7 @@ use ratatui::Frame;
use std::net::IpAddr;
use std::rc::Rc;
use trippy::dns::{AsInfo, DnsEntry, DnsResolver, Resolved, Resolver, Unresolved};
use trippy::tracing::{Extension, Extensions};

/// Render the table of data about the hops.
///
Expand Down Expand Up @@ -259,6 +260,22 @@ fn format_address(
format!("{hostname} ({addr})")
}
};

// TODO factor this out so it can be used in detail mode as well

let exp_fmt = format_extensions(config, hop);

// let exp_fmt = if let Some(extensions) = hop.extensions() {
// match config.icmp_extension_mode {
// IcmpExtensionMode::Off => None,
// IcmpExtensionMode::Mpls => format_extensions_mpls(extensions),
// IcmpExtensionMode::Full => format_extensions_full(extensions),
// IcmpExtensionMode::All => Some(format_extensions_all(extensions)),
// }
// } else {
// None
// };

let geo_fmt = match config.geoip_mode {
GeoIpMode::Off => None,
GeoIpMode::Short => geoip_lookup
Expand All @@ -274,27 +291,25 @@ fn format_address(
.unwrap_or_default()
.map(|geo| geo.location()),
};
match geo_fmt {
Some(geo) if hop.addr_count() > 1 => {
format!(
"{} [{}] [{:.1}%]",
addr_fmt,
geo,
(freq as f64 / hop.total_recv() as f64) * 100_f64
)
}
Some(geo) => {
format!("{addr_fmt} [{geo}]")
}
None if hop.addr_count() > 1 => {
format!(
"{} [{:.1}%]",
addr_fmt,
(freq as f64 / hop.total_recv() as f64) * 100_f64
)
}
None => addr_fmt,
let freq_fmt = if hop.addr_count() > 1 {
Some(format!(
"{:.1}%",
(freq as f64 / hop.total_recv() as f64) * 100_f64
))
} else {
None
};
let mut address = addr_fmt;
if let Some(geo) = geo_fmt.as_deref() {
address.push_str(&format!(" [{geo}]"));
}
if let Some(exp) = exp_fmt {
address.push_str(&format!(" [{exp}]"));
}
if let Some(freq) = freq_fmt {
address.push_str(&format!(" [{freq}]"));
}
address
}

/// Format a `DnsEntry` with or without `AS` information (if available)
Expand Down Expand Up @@ -333,6 +348,102 @@ fn format_asinfo(asinfo: &AsInfo, as_mode: AsMode) -> String {
}
}

/// Format `icmp` extensions.
fn format_extensions(config: &TuiConfig, hop: &Hop) -> Option<String> {
if let Some(extensions) = hop.extensions() {
match config.icmp_extension_mode {
IcmpExtensionMode::Off => None,
IcmpExtensionMode::Mpls => format_extensions_mpls(extensions),
IcmpExtensionMode::Full => format_extensions_full(extensions),
IcmpExtensionMode::All => Some(format_extensions_all(extensions)),
}
} else {
None
}
}

/// Format MPLS extensions as: `mpls: 12345, 6789`.
///
/// If not MPLS extensions are present then None is returned.
fn format_extensions_mpls(extensions: &Extensions) -> Option<String> {
let labels = extensions
.extensions
.iter()
.filter_map(|ext| match ext {
Extension::Unknown(_) => None,
Extension::Mpls(stack) => Some(stack),
})
.flat_map(|ext| &ext.members)
.map(|mem| mem.label)
.format(", ")
.to_string();
if labels.is_empty() {
None
} else {
Some(format!("mpls: {labels}"))
}
}

/// Format all known extensions with full details.
///
/// For MPLS: `mpls: (label=48320, ttl=1, exp=0, bos=1), (label=...)`
fn format_extensions_full(extensions: &Extensions) -> Option<String> {
let formatted = extensions
.extensions
.iter()
.filter_map(|ext| match ext {
Extension::Unknown(_) => None,
Extension::Mpls(stack) => Some(stack),
})
.flat_map(|ext| &ext.members)
.map(|mem| {
format!(
"(label={}, ttl={}, exp={}, bos={})",
mem.label, mem.ttl, mem.exp, mem.bos
)
})
.format(", ")
.to_string();
if formatted.is_empty() {
None
} else {
Some(format!("mpls: {formatted}"))
}
}

/// Format all known and unknown extensions with full details.
///
/// For MPLS: `mpls: (label=48320, ttl=1, exp=0, bos=1), (label=...)`
/// For Unknown: `unknown: (class=1, sub=1, object=0b c8 c1 01)`
fn format_extensions_all(extensions: &Extensions) -> String {
let formatted = extensions
.extensions
.iter()
.map(|ext| match ext {
Extension::Unknown(unknown) => {
format!(
"unknown: (class={}, subtype={}, object={:02x})",
unknown.class_num,
unknown.class_subtype,
unknown.bytes.iter().format(" ")
)
}
Extension::Mpls(stack) => stack
.members
.iter()
.map(|mem| {
format!(
"mpls: (label={}, ttl={}, exp={}, bos={})",
mem.label, mem.ttl, mem.exp, mem.bos
)
})
.format(", ")
.to_string(),
})
.format(", ");
format!("{formatted}")
}

/// Render hostname table cell (detailed mode).
fn render_hostname_with_details(
app: &TuiApp,
Expand Down
Loading

0 comments on commit ef3a64a

Please sign in to comment.