231 Netfilter: Deep Dive into Linux Network Filtering Core Architecture and HOOK Mechanism

Preface

Deep within the Linux kernel network subsystem lies a core framework hailed as the “backbone of network firewalls” — Netfilter. Whether it’s iptables, nftables, Docker networking, or Kubernetes Service implementations, they all rely on Netfilter’s HOOK mechanism for their fundamental capabilities. Understanding Netfilter is a prerequisite for mastering Linux network security.

This article delves into Netfilter’s framework design, HOOK registration mechanism, packet processing flow, and demonstrates HOOK operation through runnable kernel module examples, while comparing implementation differences with Windows WFP (Winsock Filtering Platform) to help you navigate the migration of network security logic across heterogeneous systems.


1. Netfilter Framework Overview

1.1 What Is Netfilter?

Netfilter is a generic packet filtering framework introduced in Linux kernel 2.4.x (2001). It sets up several “hook points” (HOOKs) along the critical path of the kernel network protocol stack, allowing kernel modules to intervene — inspecting, modifying, dropping, or accepting packets as they pass through.

Netfilter’s design philosophy can be summarized in three points:

  • Non-intrusive: Does not modify the core logic of the kernel network stack; only hooks in via HOOK points
  • Stackable: Multiple modules can hook into the same HOOK point, executing in sequence
  • Stateless: The base framework is stateless; specific rules are maintained by higher-level tools like iptables

1.2 Core Components

Netfilter consists of the following core components:

Bash
┌─────────────────────────────────────────────────────────────┐
│                    Netfilter Architecture                    │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   ┌──────────┐    ┌─────────────────┐    ┌──────────┐       │
│   │ ingress  │───▶│  NF_HOOK_PRE    │───▶│ Routing  │       │
│   │ netdev   │    │   (PREROUTING)  │    │ Decision │       │
│   └──────────┘    └─────────────────┘    └────┬─────┘       │
│                                              │              │
│          ┌───────────────────────────────────┤              │
│          ▼                                   ▼              │
│   ┌──────────────┐               ┌──────────────────┐      │
│   │ INPUT Hook   │               │ FORWARD Hook      │      │
│   │ (LOCAL_IN)   │               │ (FORWARD)         │      │
│   └──────┬───────┘               └───────┬──────────┘      │
│          │                               │                  │
│          ▼                               ▼                  │
│   ┌──────────────┐               ┌──────────────────┐      │
│   │ Application   │               │  OUTPUT Hook     │      │
│   │ (Socket)      │               │  (LOCAL_OUT)     │      │
│   └──────┬───────┘               └───────┬──────────┘      │
│          │                               │                  │
│          ▼                               ▼                  │
│   ┌──────────────┐               ┌──────────────────┐      │
│   │  POSTROUTING  │               │  POSTROUTING     │      │
│   └──────┬───────┘               └───────┬──────────┘      │
│          │                               │                  │
│          ▼                               ▼                  │
│        egress                          egress               │
└─────────────────────────────────────────────────────────────┘

2. Netfilter HOOK Points

Bash
Five HOOK points (defined in include/linux/netfilter.h):

enum nf_inet_hooks {
    NF_INET_PRE_ROUTING,    // Before routing decision
    NF_INET_LOCAL_IN,       // After routing, destined for local host
    NF_INET_FORWARD,        // After routing, not destined for local host
    NF_INET_LOCAL_OUT,      // From local process, before routing
    NF_INET_POST_ROUTING,   // After routing, before sending to network
    NF_INET_NUMHOOKS
};

Bash
The five HOOK points in the packet path:

Incoming packet (ingress):
  ┌────────────────────────────────────────────────────┐
  │  1. NF_INET_PRE_ROUTING                             │
  │     └─ Raw table (PREROUTING chain)                 │
  │     └─ Connection tracking                          │
  │     └─ mangle table (PREROUTING chain)              │
  │     └─ nat table (PREROUTING, DNAT)                 │
  └──────────────────────┬─────────────────────────────┘
                         │
                    Routing Decision
                         │
                         ├──→ Local: 2. NF_INET_LOCAL_IN
                         │           └─ mangle INPUT
                         │           └─ filter INPUT
                         │           └─ Application
                         │
                         └──→ Forward: 3. NF_INET_FORWARD
                                 └─ mangle FORWARD
                                 └─ filter FORWARD
                                 └─ 4. NF_INET_LOCAL_OUT (local output)
                                 └─ 5. NF_INET_POST_ROUTING
                                     └─ mangle POSTROUTING
                                     └─ nat POSTROUTING (SNAT/MASQUERADE)

Outgoing packet (egress):
  Process → Socket → 4. NF_INET_LOCAL_OUT
    → Routing Decision
    → 5. NF_INET_POST_ROUTING
    → Network device

3. HOOK Registration Mechanism

Bash
Kernel API for HOOK registration:

#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>

// Define a HOOK function
static unsigned int my_hook_func(
    void *priv,
    struct sk_buff *skb,
    const struct nf_hook_state *state)
{
    // Return value:
    //   NF_ACCEPT: Continue processing
    //   NF_DROP:   Drop the packet
    //   NF_QUEUE:  Queue to userspace
    //   NF_STOLEN: Stolen by module (packet processing stops)
    //   NF_REPEAT: Call the HOOK again
    return NF_ACCEPT;
}

// Define a HOOK structure (old API, Linux < 4.x)
static struct nf_hook_ops my_hook_ops __read_mostly = {
    .hook     = my_hook_func,
    .pf       = NFPROTO_IPV4,       // Protocol family
    .hooknum  = NF_INET_PRE_ROUTING, // HOOK point
    .priority = NF_IP_PRI_FIRST,     // Priority (smaller = earlier)
};

// Register/unregister
int ret = nf_register_hook(&my_hook_ops);
// ...
nf_unregister_hook(&my_hook_ops);

Bash
New API (Linux 4.x+):

// Define a HOOK structure (new API)
static struct nf_hook_ops my_hook_ops[] = {
    {
        .hook     = my_hook_func,
        .pf       = NFPROTO_IPV4,
        .hooknum  = NF_INET_PRE_ROUTING,
        .priority = NF_IP_PRI_FIRST,
    },
};

int ret = nf_register_net_hook(&init_net, my_hook_ops);
// ...
nf_unregister_net_hooks(&init_net, my_hook_ops, ARRAY_SIZE(my_hook_ops));

4. Packet Processing Flow with Demo

Bash
Complete Netfilter demo kernel module:

// netfilter_demo.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/ip.h>
#include <linux/tcp.h>

static unsigned int demo_hook(void *priv, struct sk_buff *skb,
                               const struct nf_hook_state *state)
{
    struct iphdr *iph;
    struct tcphdr *tcph;

    if (!skb)
        return NF_ACCEPT;

    iph = ip_hdr(skb);
    if (!iph)
        return NF_ACCEPT;

    // Log destination IP and port
    printk(KERN_INFO "Netfilter: SRC=%pI4 DST=%pI4 PROTO=%dn",
           &iph->saddr, &iph->daddr, iph->protocol);

    if (iph->protocol == IPPROTO_TCP) {
        tcph = tcp_hdr(skb);
        printk(KERN_INFO "  TCP: SRC_PORT=%d DST_PORT=%dn",
               ntohs(tcph->source), ntohs(tcph->dest));
    }

    return NF_ACCEPT;
}

static struct nf_hook_ops demo_ops = {
    .hook     = demo_hook,
    .pf       = NFPROTO_IPV4,
    .hooknum  = NF_INET_PRE_ROUTING,
    .priority = NF_IP_PRI_FIRST,
};

static int __init demo_init(void)
{
    nf_register_net_hook(&init_net, &demo_ops);
    printk(KERN_INFO "Netfilter demo loadedn");
    return 0;
}

static void __exit demo_exit(void)
{
    nf_unregister_net_hook(&init_net, &demo_ops);
    printk(KERN_INFO "Netfilter demo unloadedn");
}

module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");

Bash
Build and test:

$ make -C /lib/modules/$(uname -r)/build M=$PWD modules
$ insmod netfilter_demo.ko
$ dmesg -w  # Watch kernel log
$ ping 8.8.8.8
$ rmmod netfilter_demo

5. Comparison with Windows WFP

Bash
Netfilter vs. Windows WFP (Winsock Filtering Platform):

              Netfilter                     WFP
Architecture  HOOK-based kernel module      Layered driver + user-mode API
Config tools  iptables/nftables             netsh/wfpdiag
HOOK points   5 (IPv4/IPv6)                 11 layers (ALE, Stream, Datagram)
State tracking Connection tracking (conntrack)  Stateful filtering (ALE)
Performance   Very high (kernel)            Medium (user+callout)
Complexity    Medium                        High
Portability   Linux only                    Windows only

Netfilter advantages:
  - Simpler API, suitable for fast prototyping
  - Integrated with conntrack for stateful filtering
  - Higher performance (less context switching)

WFP advantages:
  - More HOOK points, finer-grained control
  - supports user-mode callout drivers (more flexible)
  - Integrated with Windows security model (AppContainer, etc.)

When migrating from Linux to Windows:
  1. Map NF_HOOK points to WFP ALE layers
  2. Replace conntrack with ALE state tracking
  3. Implement iptables rules as WFP callout drivers
Last modified: 2024年1月24日

Author

Comments

Write a Reply or Comment

Your email address will not be published.