diff --git a/README.md b/README.md index bf44733..ee4544f 100644 --- a/README.md +++ b/README.md @@ -325,9 +325,12 @@ rustnet rustnet -i eth0 rustnet --interface wlan0 -# Filter out localhost connections +# Filter out localhost connections (already filtered by default) rustnet --no-localhost +# Show localhost connections (override default filtering) +rustnet --show-localhost + # Set UI refresh interval (in milliseconds) rustnet -r 500 rustnet --refresh-interval 2000 @@ -349,7 +352,8 @@ Usage: rustnet [OPTIONS] Options: -i, --interface Network interface to monitor - --no-localhost Filter out localhost connections + --no-localhost Filter out localhost connections (default: filtered) + --show-localhost Show localhost connections (overrides default filtering) -r, --refresh-interval UI refresh interval in milliseconds [default: 1000] --no-dpi Disable deep packet inspection -l, --log-level Set the log level (if not provided, no logging will be enabled) @@ -756,7 +760,7 @@ The tool automatically detects and lists available network interfaces using plat 2. **No Connections Shown**: - Check if the correct network interface is selected - Verify packet capture permissions - - Try disabling localhost filtering with `--no-localhost` + - Try showing localhost connections with `--show-localhost` (filtered by default) 3. **High CPU Usage**: - Increase the refresh interval: `--refresh-interval 2000` diff --git a/src/cli.rs b/src/cli.rs index 5149f56..1604be8 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -19,6 +19,12 @@ pub fn build_cli() -> Command { .help("Filter out localhost connections") .action(clap::ArgAction::SetTrue), ) + .arg( + Arg::new("show-localhost") + .long("show-localhost") + .help("Show localhost connections (overrides default filtering)") + .action(clap::ArgAction::SetTrue), + ) .arg( Arg::new("refresh-interval") .short('r') diff --git a/src/main.rs b/src/main.rs index 6d0c0f5..9a531e5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -40,6 +40,11 @@ fn main() -> Result<()> { info!("Filtering localhost connections"); } + if matches.get_flag("show-localhost") { + config.filter_localhost = false; + info!("Showing localhost connections"); + } + if let Some(interval) = matches.get_one::("refresh-interval") { config.refresh_interval = *interval; info!("Using refresh interval: {}ms", interval); diff --git a/src/network/capture.rs b/src/network/capture.rs index 2351972..ef465d6 100644 --- a/src/network/capture.rs +++ b/src/network/capture.rs @@ -208,9 +208,18 @@ pub fn setup_packet_capture(config: CaptureConfig) -> Result<(Capture, S let device_name = device.name.clone(); + // Disable promiscuous mode for "any" interface on Linux + // The "any" device doesn't support promiscuous mode + let use_promisc = if device_name == "any" { + log::info!("Disabling promiscuous mode for 'any' interface (not supported)"); + false + } else { + config.promiscuous + }; + // Create capture handle let cap = Capture::from_device(device)? - .promisc(config.promiscuous) + .promisc(use_promisc) .snaplen(config.snaplen) .buffer_size(config.buffer_size) .timeout(config.timeout_ms) diff --git a/src/network/parser.rs b/src/network/parser.rs index 632bdcd..b985706 100644 --- a/src/network/parser.rs +++ b/src/network/parser.rs @@ -136,6 +136,23 @@ impl PacketParser { return self.parse_pktap_packet(data); } + // Check if this is Linux Cooked Capture (used by "any" interface on Linux) + if let Some(linktype) = self.linktype { + match linktype { + 113 => { + log::debug!("Parsing as Linux SLL (linktype 113)"); + return self.parse_linux_sll_packet(data); + } + 276 => { + log::debug!("Parsing as Linux SLL2 (linktype 276)"); + return self.parse_linux_sll2_packet(data); + } + _ => { + log::debug!("Using regular Ethernet parsing (linktype {})", linktype); + } + } + } + // Regular Ethernet parsing if data.len() < 14 { return None; @@ -147,6 +164,71 @@ impl PacketParser { 0x0800 => self.parse_ipv4_packet_inner(data, None, None), 0x86dd => self.parse_ipv6_packet_inner(data, None, None), 0x0806 => self.parse_arp_packet_inner(data, None, None), + _ => { + log::debug!("Unknown ethertype: 0x{:04x}", ethertype); + None + } + } + } + + /// Parse Linux Cooked Capture v1 packet (DLT_LINUX_SLL) + /// Header format (16 bytes): + /// - Packet type (2 bytes) + /// - ARPHRD type (2 bytes) + /// - Link-layer address length (2 bytes) + /// - Link-layer address (8 bytes) + /// - Protocol type (2 bytes) - ethertype + fn parse_linux_sll_packet(&self, data: &[u8]) -> Option { + if data.len() < 16 { + return None; + } + + // Protocol type is at bytes 14-15 + let protocol = u16::from_be_bytes([data[14], data[15]]); + + match protocol { + 0x0800 => { + // IPv4 - payload starts at byte 16 + let ip_data = &data[16..]; + self.parse_raw_ipv4_packet(ip_data, None, None) + } + 0x86dd => { + // IPv6 - payload starts at byte 16 + let ip_data = &data[16..]; + self.parse_raw_ipv6_packet(ip_data, None, None) + } + _ => None, + } + } + + /// Parse Linux Cooked Capture v2 packet (DLT_LINUX_SLL2) + /// Header format (20 bytes): + /// - Protocol type (2 bytes) - ethertype + /// - Reserved (2 bytes) + /// - Interface index (4 bytes) + /// - ARPHRD type (2 bytes) + /// - Packet type (1 byte) + /// - Link-layer address length (1 byte) + /// - Link-layer address (8 bytes) + fn parse_linux_sll2_packet(&self, data: &[u8]) -> Option { + if data.len() < 20 { + return None; + } + + // Protocol type is at bytes 0-1 + let protocol = u16::from_be_bytes([data[0], data[1]]); + + match protocol { + 0x0800 => { + // IPv4 - payload starts at byte 20 + let ip_data = &data[20..]; + self.parse_raw_ipv4_packet(ip_data, None, None) + } + 0x86dd => { + // IPv6 - payload starts at byte 20 + let ip_data = &data[20..]; + self.parse_raw_ipv6_packet(ip_data, None, None) + } _ => None, } } @@ -601,8 +683,7 @@ impl PacketParser { }) } - // Raw IP packet parsing for PKTAP DLT_RAW - #[cfg(target_os = "macos")] + // Raw IP packet parsing for PKTAP DLT_RAW and Linux Cooked Capture fn parse_raw_ipv4_packet( &self, data: &[u8], @@ -670,7 +751,6 @@ impl PacketParser { } } - #[cfg(target_os = "macos")] fn parse_raw_ipv6_packet( &self, data: &[u8],