# How Sniffnet Identifies Services Based on Well‑Known Ports: A Deep Dive into the PHF Map Implementation

> Discover how Sniffnet uses a perfect hash map to identify services with well-known ports. Learn about port-protocol mapping and service naming in this technical deep dive.

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

---

**Sniffnet identifies network services by querying a compile‑time perfect hash map that maps port‑protocol pairs to service names, scoring well‑known ports (below 1024) higher than ephemeral ports to determine the most likely service.**

Sniffnet is an open‑source network monitoring application that analyzes live traffic to display connections and their associated services. When processing packets, the application must resolve which service (HTTP, SSH, HTTPS, etc.) is running on a given port. According to the Sniffnet source code, this identification relies on a static lookup table generated at compile time and a weighted scoring algorithm that prioritizes well‑known ports.

## Build‑Time Generation of the Service Map

Sniffnet does not perform dynamic DNS queries or heuristic payload inspection to guess services. Instead, it embeds a **perfect hash map** directly into the binary.

The build process in [`build.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/build.rs) reads the [`services.txt`](https://github.com/GyulyVGC/sniffnet/blob/main/services.txt) file located at the project root. This file contains entries in the format:

```text
<service_name>\t<port>/<protocol>

```

Each line is validated and transformed using `phf_codegen` to generate a static map stored in [`OUT_DIR/services.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/OUT_DIR/services.rs). This map links `ServiceQuery` keys to `Service` values:

```rust
// build.rs – lines 33‑60
static SERVICES: phf::Map<ServiceQuery, Service> = …;

```

This approach ensures zero‑cost lookups at runtime because the map is baked into the compiled artifact.

## Runtime Service Lookup Logic

When Sniffnet captures a packet, the `get_service` function in [`src/networking/manage_packets.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/src/networking/manage_packets.rs) receives an `AddressPortPair` containing the source and destination IP addresses, ports, and the transport protocol. The function performs lookups for both the source and destination ports:

```rust
// manage_packets.rs – lines 36‑42
let service1 = SERVICES.get(&ServiceQuery(port1, key.protocol)).unwrap_or(&unknown);
let service2 = SERVICES.get(&ServiceQuery(port2, key.protocol)).unwrap_or(&unknown);

```

If neither port exists in the map, the function returns `Service::Unknown`. For ICMP or ARP traffic, it returns `Service::NotApplicable`.

### The ServiceQuery Key Structure

The lookup key is defined in [`src/networking/types/service_query.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/src/networking/types/service_query.rs) as a simple tuple struct wrapping the port number and protocol:

```rust
ServiceQuery(u16, Protocol)

```

This lightweight structure enables efficient hashing and comparison against the static `SERVICES` map generated during the build phase.

### Scoring Well‑Known vs. Ephemeral Ports

When both the source and destination ports match known services, Sniffnet must decide which name to report. It calculates a **service score** using a closure defined at lines 28‑33 of [`manage_packets.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/manage_packets.rs):

```rust
// manage_packets.rs – lines 28‑33
let service_is_some = u8::from(matches!(service, Service::Name(_)));
let port_is_well_known = if port < 1024 { 3 } else { 1 };
let bonus_direction = u8::from(bonus_direction);
service_is_some * (port_is_well_known + bonus_direction)

```

The algorithm applies the following weights:

- **Well‑known ports** (`port < 1024`) receive a weight of **3**.
- **Ephemeral or registered ports** (1024 and above) receive a weight of **1**.
- An additional **+1 bonus** is added when the remote port (or destination port in multicast scenarios) is more likely to identify the service based on traffic direction.

The service with the higher score wins. If both ports have equal scores, the logic favors the port that aligns with the traffic direction heuristic.

## The Service Enum and Type System

The result of the identification process is stored in the `Service` enum defined in [`src/networking/types/service.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/src/networking/types/service.rs):

```rust
// src/networking/types/service.rs
pub enum Service {
    Name(&'static str),   // known service, e.g. "https"
    Unknown,              // not found in the map
    NotApplicable,        // ICMP/ARP traffic
}

```

Using a `&'static str` for known services avoids runtime allocations and keeps the binary size efficient. The `Unknown` variant handles ports not listed in [`services.txt`](https://github.com/GyulyVGC/sniffnet/blob/main/services.txt), while `NotApplicable` covers non‑transport protocols.

## Practical Implementation Example

The following example demonstrates how Sniffnet identifies HTTPS traffic on port 443:

```rust
use sniffet::networking::{
    types::{AddressPortPair, Service, TrafficDirection, Protocol},
    manage_packets::get_service,
};
use std::net::{IpAddr, Ipv4Addr};
use pcap::Address;

// Simulate an outgoing TCP connection to port 443
let key = AddressPortPair::new(
    IpAddr::V4(Ipv4Addr::UNSPECIFIED), // source IP
    Some(54321),                       // ephemeral source port
    IpAddr::V4(Ipv4Addr::UNSPECIFIED), // destination IP
    Some(443),                         // well‑known HTTPS port
    Protocol::TCP,
);

let direction = TrafficDirection::Outgoing;
let interfaces: &[Address] = &[];

let service = get_service(&key, direction, interfaces);
println!("Identified service: {}", service); // Output: "https"

```

The weighting algorithm ensures that services on well‑known ports are preferred over high‑numbered ephemeral ports:

```rust
// Source port 22 (SSH, well‑known) vs destination port 8080 (HTTP alternative, not well‑known)
let key = AddressPortPair::new(
    IpAddr::V4(Ipv4Addr::UNSPECIFIED), Some(22),   // source: SSH
    IpAddr::V4(Ipv4Addr::UNSPECIFIED), Some(8080), // destination: http‑alt
    Protocol::TCP,
);

let service = get_service(&key, TrafficDirection::Outgoing, &[]);
assert_eq!(service, Service::Name("ssh")); // Port 22 scores higher (3 vs 1)

```

## Summary

- Sniffnet uses a **compile‑time perfect hash map** generated by [`build.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/build.rs) from the [`services.txt`](https://github.com/GyulyVGC/sniffnet/blob/main/services.txt) file to avoid runtime overhead.
- The `get_service` function in [`src/networking/manage_packets.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/src/networking/manage_packets.rs) performs parallel lookups on both source and destination ports using the `ServiceQuery` key.
- A **scoring algorithm** weights well‑known ports (those below 1024) three times higher than ephemeral ports to disambiguate which side of a connection defines the service.
- The `Service` enum efficiently represents known services, unknown ports, and non‑applicable protocols without heap allocations.

## Frequently Asked Questions

### How does Sniffnet handle ports that are not listed in the services.txt file?

When a port is not found in the compile‑time `SERVICES` map, the `get_service` function returns `Service::Unknown`. This variant indicates that the traffic is using a port not registered in the Internet Assigned Numbers Authority (IANA) list or the curated [`services.txt`](https://github.com/GyulyVGC/sniffnet/blob/main/services.txt) file.

### Why does Sniffnet use a perfect hash map instead of a standard HashMap?

Sniffnet uses `phf_codegen` to generate a **perfect hash function** at build time because it guarantees constant‑time lookups with no collisions and zero runtime initialization cost. Unlike `std::collections::HashMap`, the PHF map requires no dynamic memory allocation and has minimal binary size overhead, which is critical for a real‑time packet analysis tool.

### What is the significance of the port number 1024 in Sniffnet's scoring algorithm?

Ports below 1024 are reserved as **well‑known ports** by IANA and typically require root privileges to bind on Unix systems. Sniffnet exploits this convention by assigning these ports a weight of **3** versus **1** for higher ports, making it statistically more likely that the correct service is identified when both ends of a connection use known services.

### Can Sniffnet identify services running on non‑standard ports?

No. Sniffnet relies strictly on the static port‑to‑service mapping defined in [`services.txt`](https://github.com/GyulyVGC/sniffnet/blob/main/services.txt). If a service like HTTP runs on port 8080 instead of 80, it will only be identified as "http" if that specific mapping exists in the source file. The application does not perform deep packet inspection or behavioral analysis to detect services on arbitrary ports.