Kelick Tech Hub

Build, learn & keep up — tech, Linux & sports news included.

C++ Network Packet Sniffer Project

Advanced•1–2 weeks•raw sockets, packet parsing, networking

Capture live traffic and decode Ethernet, IP, and TCP headers by hand.

Share on X

What you will build

Open a raw socket, capture packets flying past your machine, and decode their headers field by field — protocol, source, destination, ports, flags. It's how Wireshark begins, and it will permanently change how you think about 'the network'. (Run it only on networks you own or have permission to monitor.)

What you will learn

Feature checklist

Starter code

// Linux raw-socket sniffer sketch (needs sudo / CAP_NET_RAW)
#include <iostream>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <linux/if_packet.h>
#include <arpa/inet.h>
#include <unistd.h>

int main() {
    int sock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
    if (sock < 0) { std::perror("socket (run with sudo)"); return 1; }

    unsigned char buf[65536];
    while (true) {
        ssize_t n = recvfrom(sock, buf, sizeof(buf), 0, nullptr, nullptr);
        if (n < 14) continue;                    // too small for Ethernet
        unsigned short ethType = (buf[12] << 8) | buf[13];
        if (ethType != 0x0800) continue;         // IPv4 only

        auto* ip = (iphdr*)(buf + 14);
        char src[INET_ADDRSTRLEN], dst[INET_ADDRSTRLEN];
        inet_ntop(AF_INET, &ip->saddr, src, sizeof(src));
        inet_ntop(AF_INET, &ip->daddr, dst, sizeof(dst));
        std::cout << "IPv4 " << src << " -> " << dst
                  << " proto=" << (int)ip->protocol
                  << " len=" << ntohs(ip->tot_len) << "\n";
        if (ip->protocol == 6) {                 // TCP
            auto* tcp = (tcphdr*)(buf + 14 + ip->ihl * 4);
            std::cout << "   TCP " << ntohs(tcp->source)
                      << " -> " << ntohs(tcp->dest) << "\n";
        }
    }
}

Full walkthrough

Step 1: Open a raw packet socket

socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)) asks the kernel for a copy of EVERY Ethernet frame on the interface — ETH_P_ALL is the firehose setting. This requires root/CAP_NET_RAW because it genuinely is wiretapping hardware; the program should check for failure and say 'run with sudo' instead of dying cryptically on EPERM.

Step 2: recvfrom() into a buffer, in a loop

Each recvfrom returns one complete frame into a byte buffer, with the sender address telling you the interface and packet type. The loop is the whole main(): receive, decode, print, count, repeat, until a packet budget runs out or Ctrl+C. Everything interesting happens in the decode.

Step 3: Decode the Ethernet header

The first 14 bytes are struct ethhdr (from <linux/if_ether.h> — the starter pulls the pieces from <netinet> headers): 6 bytes destination MAC, 6 source MAC, 2 bytes EtherType. EtherType 0x0800 means the payload is IPv4 — print MACs in hex and branch on the type. Bytes are in NETWORK order; multi-byte fields need ntohs() before they mean anything.

auto* eth = (ethhdr*)buf;
std::cout << "ETH " << macStr(eth->h_source) << " -> "
          << macStr(eth->h_dest)
          << " type 0x" << std::hex << ntohs(eth->h_proto) << std::dec;

Step 4: Decode IPv4 on top of it

If EtherType is IPv4, reinterpret the bytes after the Ethernet header as struct iphdr: source/dest addresses (inet_ntoa), total length, TTL, and the protocol field — 6 = TCP, 17 = UDP, 1 = ICMP. The header length is ihl × 4 bytes, NOT a fixed 20 — options exist, and hard-coding 20 misaligns everything downstream on the rare packets that carry them.

Step 5: Decode TCP/UDP ports and TCP flags

The transport header begins at eth + ip-header-length: struct tcphdr gives source/dest ports (ntohs both) and the flag bits — SYN, ACK, FIN, RST — which tell the story of the connection (a lone SYN is a knock; SYN+ACK is the answer). UDP's header is a fixed 8 bytes with ports and length. Print the five-tuple (src:port → dst:port, protocol) — that line IS a packet summary, in every tool from tcpdump down to this one.

Step 6: Filter, count, and hex-dump on demand

A filter option (only DNS = UDP port 53, only port 80, only ICMP) compares against the decoded fields before printing. Counters per protocol accumulate regardless of the filter, so the summary ('TCP 812 · UDP 143 · ICMP 6 · other 21') describes ALL traffic while the display shows the slice you asked for. A hex+ASCII dump of the first N bytes per packet is the final feature — the moment the bytes stop being abstract.

Step 7: Test where you know the traffic

Run it, then generate known packets: ping (ICMP appears, once per second, countable), load one web page over HTTP on a test setup (a SYN burst, then ACKs with growing payload sizes), do one DNS lookup (a single UDP pair to port 53). If your sniffer's story matches the story you deliberately caused, the decoders are aligned — that cause-and-effect test is the only trustworthy one for code that reads the world.

Common mistakes

Compile and run

g++ -std=c++17 -o sniffer sniffer.cpp

Linux only, and it must run with raw-socket privileges: sudo ./sniffer (or set CAP_NET_RAW). It uses AF_PACKET sockets, which Windows does not provide (the Windows equivalent is WinPcap/Npcap with a different API). Pick a quiet interface for your first run or the output will be a firehose.

Sample run

$ sudo ./sniffer
Sniffing on eth0 — all protocols (filter: none). Ctrl+C to stop.
ETH 3c:7a:9f:11:22:08 -> a4:83:e7:5d:90:1b type 0x0800 (IPv4)
  TCP 192.168.1.5:52344 -> 142.250.74.46:443 [SYN]
  TCP 142.250.74.46:443 -> 192.168.1.5:52344 [SYN, ACK]
  TCP 192.168.1.5:52344 -> 142.250.74.46:443 [ACK] (612 bytes of payload)
  UDP 192.168.1.5:53110 -> 8.8.8.8:53 (DNS query, 41 bytes)
  UDP 8.8.8.8:53 -> 192.168.1.5:53110 (DNS answer, 57 bytes)
  ICMP 192.168.1.5 -> 192.168.1.1 (echo request)
  ICMP 192.168.1.1 -> 192.168.1.5 (echo reply)
^C
Captured 982 packets — TCP 701 · UDP 233 · ICMP 12 · other 36

Try it in your browser

No browser demo is included for this project. Build the C++ version below — that is the real program.

Related Advanced projects

← All 60 projects · Classic site (Learn & Sandbox)

Download Fake GPS