# How Sniffnet Detects Traffic Direction: IP Address Analysis and Port-Based Heuristics

> Discover how Sniffnet detects traffic direction using IP address analysis and port-based heuristics. Understand packet flow and network activity with this detailed explanation.

- Repository: [Giuliano Bellini/sniffnet](https://github.com/GyulyVGC/sniffnet)
- Tags: internals
- Published: 2026-04-28

---

**Sniffnet determines packet direction by comparing source and destination IP addresses against the capture interface's assigned addresses, applying special port-based logic for loopback traffic and handling edge cases like DHCP requests with unspecified source addresses.**

Sniffnet, an open-source network traffic analyzer written in Rust, classifies every captured packet as either **incoming** or **outgoing** to enable accurate statistics and visualization. Understanding the mechanism for detecting traffic direction in Sniffnet requires examining how the application distinguishes local from remote traffic without relying on operating system APIs. The implementation centers on the `get_traffic_direction` function in [`src/networking/manage_packets.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/src/networking/manage_packets.rs), which evaluates IP locality against the network interface's assigned address list.

## The TrafficDirection Enum

Sniffnet represents traffic direction using a simple enum defined in [`src/networking/types/traffic_direction.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/src/networking/types/traffic_direction.rs). This enum provides two variants to classify packet flow:

```rust
/// Enum representing the possible traffic direction (incoming or outgoing).
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub enum TrafficDirection {
    /// Incoming traffic (from remote address to local interface)
    #[default]
    Incoming,
    /// Outgoing traffic (from local interface to remote address)
    Outgoing,
}

```

According to the Sniffnet source code, `Incoming` serves as the default variant, though the `get_traffic_direction` function explicitly returns the appropriate variant based on packet analysis.

## The Direction Detection Algorithm

The core logic for detecting traffic direction resides in `get_traffic_direction` within [`src/networking/manage_packets.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/src/networking/manage_packets.rs). This function receives the packet's source and destination IP addresses, optional port numbers, and a slice of `pcap::Address` structs representing every IPv4 and IPv6 address assigned to the capture interface.

### Loopback Interface Handling

Sniffnet applies a special heuristic for loopback traffic where both source and destination IPs are loopback addresses (`127.0.0.1` or `::1`). Since the same host appears as both sender and receiver, the function compares the source and destination ports:

- If the **source port is higher** than the destination port, the packet is classified as **outgoing**
- Otherwise, the packet is classified as **incoming**

This port comparison identifies which side of a local connection initiated the communication.

### Local Address Validation

The function determines IP locality through an `is_local` closure that checks whether an address belongs to `my_interface_addresses`. When analyzing offline PCAP files where the interface address list is empty, Sniffnet falls back to **bogon detection** using the `is_bogon` function to infer locality. This ensures traffic direction detection works even without access to the original network interface configuration.

### The Decision Tree

After handling the loopback special case, `get_traffic_direction` follows this logic sequence:

1. **Source is local** → Returns `TrafficDirection::Outgoing` (packet left the local host)
2. **Source is non-local and not unspecified** (`0.0.0.0` / `::`) → Returns `TrafficDirection::Incoming` (packet arrived from a remote host)
3. **Source is unspecified** (`0.0.0.0` or `::`) **and** destination is not local → Returns `TrafficDirection::Outgoing` (handles DHCP discovery and similar broadcast traffic)
4. **Otherwise** → Returns `TrafficDirection::Incoming` (covers traffic addressed to a local IP)

The full implementation (trimmed for brevity) appears as follows:

```rust
fn get_traffic_direction(
    source_ip: &IpAddr,
    destination_ip: &IpAddr,
    source_port: Option<u16>,
    dest_port: Option<u16>,
    my_interface_addresses: &[Address],
) -> TrafficDirection {
    // 1️⃣ Loopback special case
    if source_ip.is_loopback()
        && destination_ip.is_loopback()
        && let (Some(sport), Some(dport)) = (source_port, dest_port)
    {
        return if sport > dport {
            TrafficDirection::Outgoing
        } else {
            TrafficDirection::Incoming
        };
    }

    // 2️⃣ Helper to check if an IP belongs to the capture interface
    let is_local = |ip: &IpAddr| -> bool {
        if my_interface_addresses.is_empty() {
            // Offline capture – treat bogon addresses as “local”
            is_bogon(ip).is_some()
        } else {
            my_interface_addresses.iter().any(|a| a.addr == *ip)
        }
    };

    // 3️⃣ Main decision tree
    if is_local(source_ip) {
        TrafficDirection::Outgoing
    } else if source_ip.ne(&IpAddr::V4(Ipv4Addr::UNSPECIFIED))
        && source_ip.ne(&IpAddr::V6(Ipv6Addr::UNSPECIFIED))
    {
        TrafficDirection::Incoming
    } else if !is_local(destination_ip) {
        TrafficDirection::Outgoing
    } else {
        TrafficDirection::Incoming
    }
}

```

## Practical Implementation Examples

### Example 1: Standard Traffic from Local Host to Remote Server

```rust
use std::net::{IpAddr, Ipv4Addr};
use pcap::Address;
use sniffnet::networking::manage_packets::get_traffic_direction;
use sniffnet::networking::types::traffic_direction::TrafficDirection;

// Interface addresses (normally obtained from CaptureSource::get_addresses())
let iface_addrs = vec![
    Address {
        addr: IpAddr::V4("192.168.1.42".parse().unwrap()),
        netmask: Some(IpAddr::V4("255.255.255.0".parse().unwrap())),
        broadcast_addr: Some(IpAddr::V4("192.168.1.255".parse().unwrap())),
        dst_addr: None,
    },
];

// Packet from local host to example.com
let src = IpAddr::V4("192.168.1.42".parse().unwrap());
let dst = IpAddr::V4("93.184.216.34".parse().unwrap());
let dir = get_traffic_direction(&src, &dst, Some(54321), Some(80), &iface_addrs);
assert_eq!(dir, TrafficDirection::Outgoing);

```

### Example 2: DHCP Discovery with Unspecified Source

```rust
let src = IpAddr::V4(Ipv4Addr::UNSPECIFIED); // 0.0.0.0
let dst = IpAddr::V4("255.255.255.255".parse().unwrap()); // broadcast
let dir = get_traffic_direction(&src, &dst, None, Some(67), &iface_addrs);
assert_eq!(dir, TrafficDirection::Outgoing);

```

### Example 3: Loopback Traffic Classification

```rust
let src = IpAddr::V4(Ipv4Addr::LOCALHOST);
let dst = IpAddr::V4(Ipv4Addr::LOCALHOST);
// Source port > destination port indicates outgoing traffic
let dir = get_traffic_direction(&src, &dst, Some(60000), Some(5000), &[]);
assert_eq!(dir, TrafficDirection::Outgoing);

```

## Integration with Packet Flow Processing

The `modify_or_insert_in_map` function serves as Sniffnet's central packet-processing routine, calling `get_traffic_direction` for every new flow key:

```rust
let traffic_direction = get_traffic_direction(
    source_ip,
    destination_ip,
    key.sport,
    key.dport,
    my_interface_addresses,
);

```

As implemented in `GyulyVGC/sniffnet`, the resulting `TrafficDirection` value is stored in the flow's `InfoAddressPortPair` structure (defined in [`src/networking/types/address_port_pair.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/src/networking/types/address_port_pair.rs)). This classification enables the application to aggregate accurate statistics, render directional indicators in the UI, and determine which remote addresses require reverse DNS lookups.

## Summary

- **TrafficDirection Enum**: Defined in [`src/networking/types/traffic_direction.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/src/networking/types/traffic_direction.rs) with `Incoming` and `Outgoing` variants to represent packet flow direction.
- **Core Algorithm**: The `get_traffic_direction` function in [`src/networking/manage_packets.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/src/networking/manage_packets.rs) implements the classification logic using IP address locality checks.
- **Loopback Handling**: Uses port number comparison (higher port = outgoing) when both source and destination are loopback addresses.
- **Offline Support**: Falls back to bogon detection when interface addresses are unavailable, ensuring PCAP analysis works correctly.
- **Edge Cases**: Correctly handles DHCP requests with `0.0.0.0` sources by checking destination locality.
- **Integration**: Results are stored in `InfoAddressPortPair` structures for use in statistics aggregation and UI visualization.

## Frequently Asked Questions

### How does Sniffnet handle loopback traffic direction detection?

When both source and destination IP addresses are loopback addresses, Sniffnet compares the source and destination ports. If the source port is numerically higher than the destination port, the packet is classified as outgoing; otherwise, it is classified as incoming. This heuristic effectively identifies which side of a local connection initiated the communication, as implemented in the `get_traffic_direction` function.

### What happens when Sniffnet analyzes an offline PCAP file?

When the `my_interface_addresses` slice is empty—which occurs when analyzing offline PCAP files without active interface bindings—Sniffnet falls back to **bogon detection** using the `is_bogon` function. This fallback allows the application to infer address locality and correctly classify traffic direction even without access to the original network interface configuration.

### How does Sniffnet classify packets with 0.0.0.0 or :: source addresses?

Packets with unspecified source addresses (IPv4 `0.0.0.0` or IPv6 `::`) are classified as **outgoing** if the destination is not a local address, which correctly handles DHCP discovery and similar bootstrap traffic. If the destination is local, the packet is classified as incoming. This logic is part of the decision tree in [`src/networking/manage_packets.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/src/networking/manage_packets.rs).

### Where is the traffic direction stored after classification?

The `get_traffic_direction` function returns a `TrafficDirection` enum variant that is immediately stored in the `InfoAddressPortPair` structure by the `modify_or_insert_in_map` function. This value persists throughout the flow's lifetime and is used for aggregating connection statistics, rendering directional indicators in the Sniffnet UI, and determining which addresses require reverse DNS resolution.