eBPF port scanning detection
Security Tools
William  

Detect Port Scans with eBPF

The eBPF port scanning detection approach shows how kernel-level programs can answer TCP SYNs without leaving the fast path.

I guide you through a compact XDP example that reads Ethernet, IPv4, and TCP headers in the kernel. You will see how a simple program spots a SYN and replies with a SYN-ACK to emulate an open service for validation.

The demo focuses on practical steps. It filters IPv4/TCP and a target range to reduce overhead. It swaps MAC, IP, and ports, then sends the packet back via XDP_TX so responses appear immediately on the network.

This write-up also flags safety gaps. The simple demo omits checksum fixes and uses a fixed sequence number. I explain where to add checksum recalculation, correct sequence handling, and user-space maps so you can run this safely alongside real services.

Table of Contents

Key Takeaways

  • You will build an XDP-based program that reacts to SYNs in the kernel fast path.
  • Understand how SYN, SYN-ACK, and RST-ACK reveal open or closed services.
  • Filter IPv4/TCP and a target range to cut traffic and keep accuracy.
  • Modify and transmit packets from the kernel, but add checksums and proper sequence handling for safety.
  • Use user-space maps for config and metrics and tools like bpftool to monitor activity.

What you’ll build and why it matters

Follow a short, repeatable example that captures SYN probes at the ingress and forwards events for monitoring.

You will build a small XDP program that reads Ethernet, IPv4, and TCP headers at the NIC. The program flags SYN-only probes in a target port range and emits events to user space via maps or perf events. You will confirm behavior with Nmap -sS and tune lists and thresholds.

Use simple tools to load and inspect the code. When you need HTTP-layer context, switch to syscall tracing with BCC. Keep server workloads safe by skipping established flows and sockets that listen.

  • Minimal code path: check EtherType, IPv4 proto, TCP flags, and destination port.
  • Use maps so user space sets ranges, allowlists, and thresholds without rebuilds.
  • Expose counters per source IP, per port, and per interface for monitoring dashboards.

ActionComponentCommand / ExampleResult
LoadXDP programip link set dev eth0 xdp obj xdp.oProgram attached at NIC ingress
VerifyEventsbpftool map lookup name events key 1Receive information about SYN probes per IP
TestScannernmap -sS -p 80-90 targetCompare open vs closed behavior in server logs

How SYN scans work at the TCP layer

A client begins a TCP session by sending a SYN. This is a single short packet with the SYN flag set. It contains only a few bytes of header information.

A listening service replies with a SYN-ACK. That reply tells the client the socket is ready. The server also allocates an entry in the SYN queue for a short time.

The client completes the three-way handshake by sending an ACK. Only then does the server move the entry into the accept queue and hand it to the application.

Open vs closed behavior

If the port is closed, the host sends a RST-ACK. That response allocates no state. It is fast and cheap for the remote system.

  • A TCP session starts with one SYN packet of a few bytes.
  • Listening sockets reply SYN-ACK and use the SYN queue briefly.
  • The final ACK completes the handshake and creates the socket for the app.
  • Closed services answer with RST-ACK and allocate no state.

Nmap -sS sends a SYN and reads the next packet to decide open (SYN-ACK) or closed (RST-ACK). It then stops before sending the final ACK. This reduces logging for simple filters.

From a system view, the pattern is many SYN packets without matching ACKs. The function of our code is to count and classify that pattern over time.

Choosing an eBPF approach: XDP packet path vs syscall tracing

Decide between NIC-level filtering and syscall tracing by weighing speed against visibility. Each approach runs in the linux kernel but serves different needs.

When to use XDP for high-speed packet filtering

Pick XDP when you need line-rate handling on heavy traffic. It runs at NIC ingress and checks only L2–L4 headers. That keeps per-packet cycles tiny.

  • Best for millions of packets per second.
  • Supports XDP_PASS, XDP_DROP, and XDP_TX for fast replies.
  • Programs stay small and focus on header logic to reduce overhead.

When to trace system calls for application-layer visibility

Use syscall tracing when you need app context. Hook accept4, read, write, and close to reconstruct flows and capture HTTP-level data.

  • BCC or libbpf lets you link read/write events to file descriptors.
  • Tracing emits structured records for timing and function outcomes.
  • Good for troubleshooting and payload-aware analysis.

In practice, combine both. Run XDP to flag high-rate anomalies and a tracing program to sample suspicious connections. Keep each program focused. Test changes in a lab to avoid accidental drops or latency spikes.

Plan the eBPF/XDP program for network traffic inspection

Start by defining simple header checks so the kernel work stays light and predictable.

Parse the Ethernet header and check the EtherType equals IPv4. If it is not IPv4, return XDP_PASS to avoid wasted cycles.

Identify IPv4 and TCP packets in the kernel

From the IPv4 header, confirm the protocol is TCP. Pass UDP and ICMP quickly. Use bounds checks before reading the TCP header so the verifier accepts the code.

Target ports and filter ranges without excess overhead

Extract the destination port and compare it against a range, for example 9000–9500. Use a hash map for watched ports so you can update the set from user space.

  • Keep a per-source counter in a map for SYN-only attempts.
  • Store only small items: flags, ports, and counts. Avoid copying payload data.
  • Use a config map to toggle simulation mode and prevent interference with real services.
  • Write the code in clear, small functions so the verifier can follow types and control flow.
StepHeader checkActionWhy
1EtherType == IPv4XDP_PASS otherwiseSaves CPU by skipping non-IP types
2IPv4 proto == TCPXDP_PASS otherwiseKeeps program small and safe
3TCP dest port in 9000–9500 or mapUpdate per-src counterAllows user-space thresholds and response

When you need examples for user-space maps and helpers, see this short guide to start programming with BCC tools: start with BCC tools.

Implement the XDP logic: filter, detect SYN, respond

I will walk you through a compact XDP flow that filters packets, flags SYN-only attempts, and crafts a SYN-ACK reply from the kernel fast path.

Keep every read and write guarded by bounds checks. Get mutable pointers to the Ethernet, IPv4, and TCP headers. Verify EtherType == IPv4 and IPv4 proto == TCP before any header access.

  • Use a map or a numeric range (9000–9500) to filter destination ports. This limits work to relevant traffic.
  • Detect SYN-only packets by testing SYN==1 and ACK==0. Pass other packets to avoid breaking real sessions.
  • Swap L2 MACs. Swap L3 IPv4 addresses. Swap TCP source and destination ports.
  • Set the TCP ACK flag. Compute ack_seq = seq + 1. Set seq = 1 (use a secure initial sequence in production).
  • Update a counter map for packets inspected and responses sent. This helps measure traffic and tune thresholds.
  • Return XDP_TX so the modified packet goes back out on the same interface with minimal time in the kernel.

Concrete header manipulations in code follow verifier-friendly patterns: check lengths, then cast to EthHdr*, Ipv4Hdr*, and TcpHdr*. Do not access fields before checks pass.

ActionHeader FieldValue/Operation
Ethernetdst/src MACSwap source and destination MAC addresses
IPv4src/dst IPSwap and preserve TTL; recalc header checksum in prod
TCPflags, seq, ack_seq, portsSet ACK, ack_seq=in_seq+1, seq=start_seq; swap ports; recalc checksum

Note the checksum and sequence caveats. Test setups and some raw sockets let you skip checksum fixes, but production code must recalculate IP and TCP checksums and use secure initial sequence numbers. Skipping those steps can cause peers to drop the response or create subtle runtime issues.

Coordinate with user space for control and visibility

Keep kernel work minimal and let a small user process handle configuration and aggregation. Run simple, verifier-friendly programs in the kernel and move stateful logic to user space. This reduces risk and gives you flexible control.

Use maps for lists, thresholds, and counters. Pin maps in bpffs so file-backed maps survive reloads. Send compact events to user space with a ring buffer or perf output. A small user agent reads events and writes JSON lines for easy parsing.

Practical setup

  • Keep program size small and store toggles in maps writable from user space.
  • Use perf/ring buffer to emit events with timestamp, src IP, dst port, and code.
  • Store counters per source IP and per port and export via a Prometheus exporter.
  • Pin config and metrics as separate files in bpffs to avoid accidental loss.
  • Bound map sizes and use LRU maps to limit memory use under load.
ItemLocationPurpose
Config mapbpffs pinned fileEditable list of simulated open ports and toggles
Event ringperf/ring bufferCompact events to user space for logging and monitoring
Counters mappinned mapPer-src and per-port metrics for exporter

Validate user space writes before accepting them. Run the user process with least privilege and only the caps it needs. This keeps the system safe while giving you good monitoring and control.

Validate results with nmap and real traffic

Run a controlled scan and compare outputs to confirm your kernel reply logic works as intended.

Use this exact command from a tester machine or the server itself:

sudo nmap -sS -p9000-9500 <your_ip>

As root, nmap uses raw sockets and shows whether the server replies with SYN-ACK or reset. If your program crafts SYN-ACK replies, nmap will report those ports as open. If the host sends a reset, the ports show as closed.

Quick interpretation and tuning

  • Run the command before and after loading the program to compare output and confirm changes.
  • Capture a few packets with tcpdump on a mirrored interface to see SYN and SYN-ACK pairs for packet-level analysis.
  • If nothing changes, verify the program is attached to the correct interface and the IP used in the command is correct.
  • If peers ignore replies, check checksum offload or missing recalculation as the likely cause.
  • Adjust the simulated range in your config map and rerun the scan to confirm dynamic updates.
ActionCommand / ExampleExpected output
Scan testsudo nmap -sS -p9000-9500 <your_ip>Ports in simulated range: open. Others: closed/reset.
Packet capturesudo tcpdump -i mirror ‘tcp[tcpflags] & (tcp-syn) != 0’See SYN from scanner and SYN-ACK from server/program.
Troubleshootbpftool show; ip link showConfirm program attached and correct interface selected.

Production hardening and safety checks

Production use demands strict safeguards to prevent kernel regressions and service disruption.

Recalculate checksums and handle sequence numbers

Always recompute IP and TCP checksums when you change headers. Many stacks drop packets with bad checksums. Use cryptographic-quality initial sequence numbers. A fixed value is acceptable only in a lab.

Avoid impact on legitimate servers and sockets

Do not simulate on ports with real services. Detect established flows and pass them unchanged. Limit attachment to interfaces that need monitoring to reduce blast radius.

Gate loading with least privilege and policy

Require signed binaries and restrict who can load kernel objects. Set CONFIG_BPF_UNPRIV_DEFAULT_OFF and avoid CONFIG_BPF_KPROBE_OVERRIDE. Limit SYS_bpf usage and enforce role-based policies.

SafeguardActionWhy it matters
Checksum recomputeRecalc IP/TCP after editsPrevents packet drops by peers
Secure ISNsUse crypto-quality sequencesProtects against injection and prediction
Scope limitsAttach per-interface onlyReduces kernel exposure and risk
Policy & signingSigned files; RBAC for loadingControls who can change runtime behavior
Memory trackingMonitor map sizes and usagePrevents OOM and degradation on busy servers

Keep a rollback runbook. Pin maps to a file, keep prior binaries, and include a detach command. Review each code change. Small edits can change verifier results and runtime safety. These steps protect your servers and improve overall security.

Detect and monitor eBPF activity on the host

A quick inventory of loaded ebpf programs and pinned maps makes audits practical and repeatable. Start with simple commands to collect baseline information and schedule regular checks.

Use bpftool to list programs, maps, and links

Run these commands and note unexpected entries, sizes, or pins.

  • bpftool prog show — lists program IDs, types, and attach points. Confirm your XDP entry and any unfamiliar programs.
  • bpftool map show — shows maps, key/value sizes, and pin paths. Watch for large maps or unknown files in /sys/fs/bpf.
  • ip link show dev <iface> and tc filter show dev <iface> — verify interface attachments and hooks.

Tracee, Falco, and LKRG for runtime events and integrity

Deploy Tracee to log bpf() loads and attach events with timestamps and PIDs. Add Falco rules to alert on unplanned bpf(), perf_event_open, or new pins. Install LKRG to guard kernel structures and block tampering.

ActionCommandWhat to check
Inventorybpftool prog showUnknown program types or attach points
Mapsbpftool map showLarge maps or unexpected pin files
AuditTracee / FalcoSuspicious events and process context

Schedule nightly diffs of bpftool output and bpffs listings. Centralize logs so you can link events to sockets and processes. Document expected programs and files so operators spot anomalies quickly.

Handling adversarial use: eBPF backdoors and stealth scans

Kernel-resident backdoors can hide in memory and never touch a file. Traditional AV looks at files on disk. That makes it blind to programs that live only in kernel memory.

Act early. Alert on bpf() calls and unexpected program attachments with Tracee or Falco. Those tools catch loads at the syscall level before an implant can tamper with bpftool output.

A dark, modern workspace with an array of computer screens displaying intricate network diagrams, terminal windows, and low-level kernel analysis. Illuminated by the glow of monitors, the scene conveys a sense of technical exploration and cybersecurity investigation. In the foreground, a high-resolution close-up of a microchip, its circuitry dissected and scrutinized. Cables and hardware components litter the desk, suggesting a deep dive into the mechanics of a potential kernel-level backdoor. The atmosphere is tense, the mood pensive, as the analyst delves into the hidden secrets of the system.

If you suspect compromise — practical triage

  • Snapshot the virtual machine memory from the hypervisor.
  • Run Volatility with eBPF plugins to extract program metadata and map state.
  • Save bpftool output and network logs to a file for later correlation.

Cloud and hypervisor notes

In OpenStack, run a small script from the host to SSH into the guest and run a bpftool-based check. Example: openstack server ssh <vm> –command ‘sudo ./scan_bpf.sh > /tmp/bpf_output.txt’.

In vSphere with NSX, you cannot inspect guest kernel memory from the network. Use distributed firewall rules, IDS/IPS, and flow analytics to flag odd east-west traffic between servers.

StepActionWhy
Load-time alertTracee / Falco watch bpf() callsMost reliable initial breakpoint
Memory forensicsHypervisor snapshot + VolatilityExtract in-memory program and map state
Network signsNSX flow analytics, IDS/IPSDetect hidden sockets or odd traffic paths

Keep a short checklist and a small toolkit on hand: Tracee, Falco, bpftool, Volatility. Train responders to link process IDs, program IDs, and traffic quickly. Limit kernel capabilities on production images to reduce the attack surface.

Next steps to apply this on your servers

Deploy the XDP program in a controlled lab first. Attach it to one machine and set a small port range in the pinned map. Run a scan with this command to verify behavior: sudo nmap -sS -p9000-9500 <your_ip>.

Add checksum recomputation and secure sequence numbers in your code before moving beyond tests. Pin maps in bpffs so you can update a file without reloads. Build a small user space helper to push config, read events, and export metrics to your monitoring stack.

Keep a runbook with attach/detach and state checks. Use bpftool to list programs and maps. Enable Tracee and Falco rules and register LKRG on images. Review logs weekly and keep a single-command rollback to detach the program if needed.

FAQ

What will I build in "Detect Port Scans with eBPF" and why does it matter?

You will build an XDP-based program that inspects IPv4/TCP packets to identify SYN-only connection attempts and flag likely scans. This matters because it gives kernel-level visibility with low latency. You can block or log suspicious traffic before it reaches user processes, improving network security and reducing load on servers and applications.

How does a SYN scan work at the TCP layer?

A SYN scan sends a TCP SYN to a target port. If the port is open the host replies with SYN-ACK. If closed it replies with RST. The scanner does not complete the three-way handshake. By watching for SYNs without a completed handshake you can infer scanning activity.

Why use XDP packet path instead of tracing syscalls?

Use XDP when you need high-speed filtering and minimal packet processing overhead. XDP runs early in the kernel network stack and can drop or modify packets before sockets or the network stack are involved. Trace syscalls when you need per-process context or to correlate network activity with specific programs.

When should I trace system calls rather than use XDP?

Trace syscalls when you need visibility into the user-space process that initiated traffic, or when you must inspect socket options, connect() attempts, or accept() behavior. Syscall tracing gives application-layer context but adds overhead and may miss packets handled entirely in kernel or bypassed by hardware.

How do I identify IPv4 and TCP packets in the kernel program?

In the XDP program parse the Ethernet header then the IPv4 header. Check the protocol field for TCP. Then parse the TCP header and inspect flags for SYN, ACK, and RST. Use safe bounds checks to avoid verifier errors and to keep memory access minimal.

How do I target ports and filter ranges without excess overhead?

Use eBPF maps to store configured port ranges or bitmaps. Match destination ports against the map rather than hardcoding many comparisons. Keep lookup paths short and prefer exact-match maps or compact range checks to reduce CPU and verifier complexity.

How does the XDP logic detect SYN-only scan attempts?

The program checks TCP flags for SYN set and ACK/RST not set. When a SYN arrives and the destination port matches a monitored set, you increment a counter or mark an event in a map. Correlate repeated SYNs from the same source to classify scanning behavior.

Can the XDP program craft SYN-ACKs and send them back? What are checksum trade-offs?

Yes. You can swap source and destination MAC/IP/ports, set TCP flags to SYN-ACK, and return via XDP_TX. You must update IP and TCP checksums. Some targets allow offloading checksum recalculation, but relying on offload can be risky. Recalculate checksums in kernel when accuracy is required.

How do I transmit modified packets with XDP_TX safely?

Ensure packet headroom and total size are valid. Perform proper byte-order conversions and recalculate checksums or mark for offload when supported. Test on a non-production interface to avoid disrupting legitimate clients or servers.

How do I coordinate with user space for control and visibility?

Expose eBPF maps for configuration and counters. Use a user-space agent to load programs, update port lists, and pull metrics via perf events or map reads. This lets you tune thresholds and export events to monitoring systems like Prometheus.

What logging and metrics should I export for monitoring?

Log SYN rates per source IP, unique source counts, and flags like repeated SYNs to many ports. Export histograms for scan duration and counters for dropped or responded packets. Keep logs compact to limit memory and syscall cost.

How can I validate detection results with nmap and real traffic?

Run nmap -sS from a test host to generate SYN scans and observe responses. Confirm open ports yield SYN-ACK and closed ports yield RST. Compare XDP counters and maps with nmap output to tune thresholds and false positives.

How should I interpret nmap output to tune thresholds?

Look for rapid SYNs to many ports from one source. Use nmap timing options to simulate different scan speeds. Tune your detection window and rate limits so normal clients are not misclassified during legitimate scans like service discovery.

What production hardening and safety checks are required?

Recalculate checksums, properly handle TCP sequence numbers if forging packets, and verify MTU and fragmentation. Gate program loading with least privilege and test for verifier acceptance. Use safe maps sizes and eviction policies to avoid DoS from map exhaustion.

How do I avoid impacting legitimate servers and sockets?

Exclude known server IPs and ports via configuration maps. Limit heuristic thresholds and add allowlists for management hosts. Monitor for performance regressions and keep paths minimal to reduce CPU impact on busy servers.

What are the privilege and policy requirements for loading programs?

Loading and attaching XDP programs requires CAP_SYS_ADMIN or equivalent. Use systemd units with proper security context or a small privileged loader to minimize attack surface. Restrict who can update maps and metrics to reduce misuse.

How can I detect and monitor eBPF activity on the host?

Use bpftool to list programs, maps, and links. Monitor kernel logs for verifier messages. Tools like Tracee and Falco can watch bpf() syscalls and attachments. Track load events and use LKRG for additional integrity checks.

Why can kernel-space backdoors evade traditional antivirus?

Traditional antivirus runs in user space and watches file or process behavior. Code running as kernel modules or attached via bpf() operates in kernel context and can modify packet handling without touching files. You must use kernel-level auditing and tooling to detect them.

What forensic steps help investigate suspected kernel-space compromise?

Capture memory and kernel objects with tools like Volatility and use hypervisor snapshots when available. Dump bpftool output, verify program bytecode, and compare maps to known baselines. Preserve timestamps and chain of custody for post-incident analysis.

What should I consider for cloud platforms like OpenStack or VMware?

Guest-level XDP runs inside VMs and may be invisible to host networking stacks. Use cloud networking tools (NSX, OpenStack Neutron) and hypervisor introspection to correlate host and guest traffic. Ensure visibility at both tenant and host levels to detect stealthy scans.

How do I test and harden on virtual machines and physical servers?

Test on isolated VMs to validate verifier behavior and performance. Run stress tests with real traffic generation. On physical servers, test with safe firewall rules and monitor kernel CPU and packet drops. Gradually roll out changes with feature flags.

What are next steps to apply this on my servers?

Start by installing bpftool and building a simple XDP program that logs SYNs to a perf buffer. Add maps for port configuration. Test with nmap -sS and tune thresholds. Integrate logging into your monitoring stack for alerts and dashboards.