Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,7 @@ Wireshark operates at the packet capture layer (libpcap) - it sees raw network t
|------------|---------|-----------|
| Process identification | Yes (eBPF, procfs, platform APIs) | No |
| Connection state tracking | Native (TCP FSM, QUIC states) | Via dissectors |
| Protocol dissectors | ~15 common protocols | 3000+ protocols |
| Protocol dissectors | ~16 common protocols | 3000+ protocols |
| Packet-level inspection | Metadata only | Full payload |
| Interface | TUI (terminal) | GUI |
| Capture to file | Yes (`--pcap-export`) | Yes (native) |
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ The headline of this release is a major TUI refresh. The tabs, stats panel, and
### Added
- **TUI Revamp**: Redesigned tabs, stats panel, and details view (#239)
- **Per-field Colors and Status Dot**: New per-field colors, status dot, and magenta panel borders for at-a-glance readability (#241)
- **SIP Protocol Detection**: Deep packet inspection for SIP (Session Initiation Protocol) traffic over UDP/TCP port 5060, with method/URI/response code display and compact header support
- **Address Scope Labels**: Remote addresses are tagged PUBLIC, PRIVATE, etc. in the connection list (#251)
- **Reverse DNS Resolution by Default**: Reverse DNS resolution is now enabled by default. Use the new `--no-resolve-dns` flag to opt out (#245)

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
## Features

- **Per-process attribution**: Every TCP, UDP, and QUIC connection mapped to its owning process, via eBPF on Linux, PKTAP on macOS, native APIs on Windows and FreeBSD. Wireshark and tcpdump can't do this; `netstat` / `ss` can't show live state.
- **Deep packet inspection**: Identify HTTP, HTTPS/TLS with SNI, DNS, SSH, QUIC, NTP, mDNS, LLMNR, DHCP, SNMP, SSDP, and NetBIOS, without external dissectors.
- **Deep packet inspection**: Identify HTTP, HTTPS/TLS with SNI, DNS, SSH, QUIC, SIP, NTP, mDNS, LLMNR, DHCP, SNMP, SSDP, and NetBIOS, without external dissectors.
- **Security sandboxing**: Landlock (Linux 5.13+), Seatbelt (macOS), token privilege drop + job-object child-process block (Windows). Drops privileges immediately after libpcap initializes. See [SECURITY.md](SECURITY.md).
- **TCP network analytics**: Real-time retransmissions, out-of-order packets, and fast-retransmit detection, per-connection and aggregate.
- **Smart connection lifecycle**: Protocol-aware timeouts with white → yellow → red staleness indicators. Toggle `t` to keep historic (closed) connections visible for forensics.
Expand Down
53 changes: 53 additions & 0 deletions src/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,59 @@ impl ConnectionFilter {
return true;
}
}
ApplicationProtocol::Sip(info) => {
if match_text("sip", fv) {
return true;
}
if let Some(ref method) = info.method
&& match_text(method, fv)
{
return true;
}
if let Some(ref request_uri) = info.request_uri
&& match_text(request_uri, fv)
{
return true;
}
if let Some(ref from) = info.from
&& match_text(from, fv)
{
return true;
}
if let Some(ref to) = info.to
&& match_text(to, fv)
{
return true;
}
if let Some(ref call_id) = info.call_id
&& match_text(call_id, fv)
{
return true;
}
if let Some(ref cseq_method) = info.cseq_method
&& match_text(cseq_method, fv)
{
return true;
}
if let Some(ref user_agent) = info.user_agent
&& match_text(user_agent, fv)
{
return true;
}
if let Some(ref server) = info.server
&& match_text(server, fv)
{
return true;
}
if let Some(ref content_type) = info.content_type
&& match_text(content_type, fv)
{
return true;
}
if info.has_sdp && match_text("sdp", fv) {
return true;
}
}
ApplicationProtocol::NetBios(info) => {
if let Some(ref name) = info.name
&& match_text(name, fv)
Expand Down
8 changes: 7 additions & 1 deletion src/network/dpi/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub fn analyze_http(payload: &[u8]) -> Option<HttpInfo> {
// Response line: HTTP/1.1 200 OK
info.version = parse_http_version(parts[0]);
info.status_code = parts[1].parse::<u16>().ok();
} else if is_http_method(parts[0]) {
} else if is_http_method(parts[0]) && parts[2].starts_with("HTTP/") {
// Request line: GET /path HTTP/1.1
info.method = Some(parts[0].to_string());
info.path = Some(parts[1].to_string());
Expand Down Expand Up @@ -127,4 +127,10 @@ mod tests {
assert_eq!(info.status_code, Some(200));
assert!(info.method.is_none());
}

#[test]
fn test_sip_options_not_http() {
let payload = b"OPTIONS sip:bob@example.com SIP/2.0\r\nVia: SIP/2.0/UDP host\r\n\r\n";
assert!(analyze_http(payload).is_none());
}
}
52 changes: 36 additions & 16 deletions src/network/dpi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ mod mqtt;
mod netbios;
mod ntp;
mod quic;
mod sip;
mod snmp;
mod ssdp;
mod ssh;
Expand All @@ -32,6 +33,7 @@ const PORT_NETBIOS_DGM: u16 = 138;
const PORT_SNMP: u16 = 161;
const PORT_SNMP_TRAP: u16 = 162;
const PORT_HTTPS: u16 = 443;
const PORT_SIP: u16 = 5060;
const PORT_MQTT: u16 = 1883;
const PORT_SSDP: u16 = 1900;
const PORT_MDNS: u16 = 5353;
Expand All @@ -58,14 +60,23 @@ pub fn analyze_tcp_packet(

// Try protocols in order of likelihood/speed

// 1. Check for HTTP (fast string matching)
// 1. Check for SIP (port 5060 or SIP start-line)
if (local_port == PORT_SIP || remote_port == PORT_SIP || sip::is_likely_sip(payload))
&& let Some(sip_result) = sip::analyze_sip(payload)
{
return Some(DpiResult {
application: ApplicationProtocol::Sip(sip_result),
});
}

// 2. Check for HTTP (fast string matching)
if let Some(http_result) = http::analyze_http(payload) {
return Some(DpiResult {
application: ApplicationProtocol::Http(http_result),
});
}

// 2. Check for TLS/HTTPS (port 443 or TLS handshake)
// 3. Check for TLS/HTTPS (port 443 or TLS handshake)
if (local_port == PORT_HTTPS || remote_port == PORT_HTTPS || https::is_tls_handshake(payload))
&& let Some(tls_result) = https::analyze_https(payload)
{
Expand All @@ -74,7 +85,7 @@ pub fn analyze_tcp_packet(
});
}

// 3. Check for BitTorrent (handshake signature \x13BitTorrent protocol)
// 4. Check for BitTorrent (handshake signature \x13BitTorrent protocol)
if bittorrent::is_bittorrent_handshake(payload)
&& let Some(bt_result) = bittorrent::analyze_bittorrent(payload)
{
Expand All @@ -83,7 +94,7 @@ pub fn analyze_tcp_packet(
});
}

// 4. Check for MQTT (port 1883 or MQTT signature)
// 5. Check for MQTT (port 1883 or MQTT signature)
if (local_port == PORT_MQTT || remote_port == PORT_MQTT || mqtt::is_mqtt_packet(payload))
&& let Some(mqtt_result) = mqtt::analyze_mqtt(payload)
{
Expand All @@ -92,7 +103,7 @@ pub fn analyze_tcp_packet(
});
}

// 5. Check for SSH (port 22 or SSH banner)
// 6. Check for SSH (port 22 or SSH banner)
if (local_port == PORT_SSH || remote_port == PORT_SSH || ssh::is_likely_ssh(payload))
&& let Some(ssh_result) = ssh::analyze_ssh(payload, _is_outgoing)
{
Expand Down Expand Up @@ -126,7 +137,16 @@ pub fn analyze_udp_packet(
});
}

// 2. QUIC/HTTP3 (port 443)
// 2. SIP (port 5060 or SIP start-line)
if (local_port == PORT_SIP || remote_port == PORT_SIP || sip::is_likely_sip(payload))
&& let Some(sip_result) = sip::analyze_sip(payload)
{
return Some(DpiResult {
application: ApplicationProtocol::Sip(sip_result),
});
}

// 3. QUIC/HTTP3 (port 443)
if (local_port == PORT_HTTPS || remote_port == PORT_HTTPS) && quic::is_quic_packet(payload) {
let quic_info = quic::parse_quic_packet(payload);
if let Some(quic_info) = quic_info {
Expand All @@ -144,7 +164,7 @@ pub fn analyze_udp_packet(
}
}

// 3. mDNS (port 5353)
// 4. mDNS (port 5353)
if (local_port == PORT_MDNS || remote_port == PORT_MDNS)
&& let Some(mdns_result) = mdns::analyze_mdns(payload)
{
Expand All @@ -153,7 +173,7 @@ pub fn analyze_udp_packet(
});
}

// 4. DHCP (ports 67-68)
// 5. DHCP (ports 67-68)
if matches!(
(local_port, remote_port),
(PORT_DHCP_SERVER, _)
Expand All @@ -167,7 +187,7 @@ pub fn analyze_udp_packet(
});
}

// 5. NTP (port 123)
// 6. NTP (port 123)
if (local_port == PORT_NTP || remote_port == PORT_NTP)
&& let Some(ntp_result) = ntp::analyze_ntp(payload)
{
Expand All @@ -176,7 +196,7 @@ pub fn analyze_udp_packet(
});
}

// 6. LLMNR (port 5355)
// 7. LLMNR (port 5355)
if (local_port == PORT_LLMNR || remote_port == PORT_LLMNR)
&& let Some(llmnr_result) = llmnr::analyze_llmnr(payload)
{
Expand All @@ -185,7 +205,7 @@ pub fn analyze_udp_packet(
});
}

// 7. SSDP (port 1900)
// 8. SSDP (port 1900)
if (local_port == PORT_SSDP || remote_port == PORT_SSDP)
&& let Some(ssdp_result) = ssdp::analyze_ssdp(payload)
{
Expand All @@ -194,7 +214,7 @@ pub fn analyze_udp_packet(
});
}

// 8. NetBIOS-NS (port 137)
// 9. NetBIOS-NS (port 137)
if (local_port == PORT_NETBIOS_NS || remote_port == PORT_NETBIOS_NS)
&& let Some(netbios_result) = netbios::analyze_netbios_ns(payload)
{
Expand All @@ -203,7 +223,7 @@ pub fn analyze_udp_packet(
});
}

// 9. NetBIOS-DGM (port 138)
// 10. NetBIOS-DGM (port 138)
if (local_port == PORT_NETBIOS_DGM || remote_port == PORT_NETBIOS_DGM)
&& let Some(netbios_result) = netbios::analyze_netbios_dgm(payload)
{
Expand All @@ -212,7 +232,7 @@ pub fn analyze_udp_packet(
});
}

// 10. SNMP (ports 161-162)
// 11. SNMP (ports 161-162)
if matches!(
(local_port, remote_port),
(PORT_SNMP, _) | (PORT_SNMP_TRAP, _) | (_, PORT_SNMP) | (_, PORT_SNMP_TRAP)
Expand All @@ -223,7 +243,7 @@ pub fn analyze_udp_packet(
});
}

// 11. STUN (port 3478/5349 or magic cookie detection for non-standard ports)
// 12. STUN (port 3478/5349 or magic cookie detection for non-standard ports)
if (local_port == PORT_STUN
|| remote_port == PORT_STUN
|| local_port == PORT_STUN_TLS
Expand All @@ -236,7 +256,7 @@ pub fn analyze_udp_packet(
});
}

// 12. BitTorrent DHT / uTP (no port gating — signature-based)
// 13. BitTorrent DHT / uTP (no port gating — signature-based)
if let Some(bt_result) = bittorrent::analyze_udp_bittorrent(payload) {
return Some(DpiResult {
application: ApplicationProtocol::BitTorrent(bt_result),
Expand Down
Loading