Netfilter framework of Linux kernel

Keywords: Linux kernel

Netfilter framework of Linux kernel

Initial publication Nuggets

The author has done some research on Linux kernel related modules to realize kernel level communication encryption and video stream encryption, including Linux kernel network protocol stack, Linux kernel communication module, Linux kernel encryption module, secret key generation and distribution, etc.
Consider setting up a Linux kernel column in the future.

Without much to say, I'll take you directly to the Netfilter framework of the Linux kernel.

1, Overview: what is Netfilter

For application layer developers who do not often contact the Linux kernel, they may know little about Netfilter. However, most Linux users have used or know iptables. However, the functional implementation of iptables is completed on Netfilter.

Netfilter framework was developed by Rusty Russell, a famous Linux developer, in 1998. It aims to improve the previous implementation of ipchains (Linux 2.2. X) and ipfwadm (Linux 2.0. X).

Netfilter Is a framework in the Linux kernel, which provides flexibility for various network related operations in the form of custom processors. Netfilter provides various options for packet filtering, network address translation and port translation.

1. Composition of Netfilter

Its detailed composition:

Netfilter is the main implementation framework for packet filtering, Connect Track, network address translation (NAT) and other functions in Linux kernel; The framework defines a series of Hook points in the key process of network protocol stack processing packets, and registers a series of functions in these Hook points to process packets. These functions registered at the Hook point are the packet passing strategy set in the network protocol stack. In other words, these functions can determine whether the kernel accepts or discards a packet, and the processing result of the function determines the "fate" of the network packet.

As can be seen from the figure, the Netfilter framework adopts the modular design concept and runs through the kernel state and user state of the Linux system.

At the user state level, different system call tools are provided for upper layer users according to different protocol types, such as iptables for IPv4 protocol, ip6tables for IPv6 protocol, arptabs for ARP protocol, ebtables for bridge control, conntrack for network connection tracking, etc.

Different user mode tools have corresponding modules in the kernel, and the bottom layer needs to call the Netfilter hook API interface for implementation.

It is also found that iptables, the Linux firewall tool mentioned earlier, is actually a component of the Netfilter framework.

2.Netfilter packet path

Path of normal packet in Netfilter:

2, Netfilter implementation

Netfilter Hooks in the Linux Kernel

1.Netfilter mount point: Netfilter places

(1) Function definition

As can be seen from the above network packet sending and receiving flow chart, Nefilter's hook function can be registered in different places. It is defined as follows:

// include/linux/netfilter.h

enum nf_inet_hooks {
        NF_INET_PRE_ROUTING,
        NF_INET_LOCAL_IN,
        NF_INET_FORWARD,
        NF_INET_LOCAL_OUT,
        NF_INET_POST_ROUTING,
        NF_INET_NUMHOOKS
};
  • NF_INET_PRE_ROUTING: incoming packets pass this hook in the () function before they are processed by the routing code. ip_rcv()``linux/net/ipv4/ip_input.c
  • NF_INET_LOCAL_IN: all incoming packets addressed to the local computer pass this hook in the function . ip_local_deliver()
  • NF_INET_FORWARD: incoming packets are passed this hook in the function . ip_forwared()
  • NF_INET_LOCAL_OUT: all outgoing packets created in the local computer pass this hook in the function . ip_build_and_send_pkt()
  • NF_INET_POST_ROUTING: this hook in the ipfinishoutput() function before they leave the computer.

(2) Mount point analysis

Netfilter filters or modifies packets by registering hook functions (Hooks) at different locations in the kernel protocol stack. These locations are called mount points, mainly including five: PRE_ROUTING,LOCAL_IN,FORWARD,LOCAL_OUT and POST_ROUTING.

Mount point resolution:

  • PRE_ROUTING: before routing. After the packet enters the IP layer, but before routing the packet.
  • LOCAL_IN: enter local. After routing the packet, if the packet is sent to the local, before sending the packet to the upper layer protocol.
  • FORWARD: FORWARD. After routing the packet, if the packet is not sent to the local, before forwarding the packet.
  • LOCAL_OUT: local output. For the output packet, before routing the packet.
  • POST_ROUTING: after routing. For the output packet, after routing the packet.

Routing decision:

As can be seen from the above figure, routing determination is the key point of data flow.

  • The first route determines whether the destination IP address of the IP header of the input packet is the IP address of the local machine. If it is the IP address of the local machine, it indicates that the data is sent to the local machine. Otherwise, it means that the data packet is sent to other hosts and is only transferred through this host.
  • The second routing decision looks up the corresponding routing information from the routing table according to the destination IP address of the output packet IP header, and then obtains the IP address of the next hop host (or gateway) according to the routing information, and then carries out data transmission.

Packet flow direction
As can be seen from the figure, the hook nodes that packets in the three directions need to pass through are not exactly the same:

  • To local: NF_INET_PRE_ROUTING–>NF_ INET_ LOCAL_ IN
  • Forwarding: NF_INET_PRE_ROUTING–>NF_ INET_ FORWARD–>NF_ INET_ POST_ ROUTING
  • Local issue: NF_INET_LOCAL_OUT–>NF_ INET_ POST_ ROUTING

(3) Mount linked list

By registering hook functions with these mount points, you can filter or modify packets at different stages. Because hook functions can register multiple, the kernel uses a linked list to store these hook functions. When the packet enters the local (LOCAL_IN mount point), IPT will be called one after another_ Hook and fw_confirm hook function to process packets. In addition, the hook function also has priority. The smaller the priority, the earlier it is executed. Because the attachment point stores hook functions through a linked list, the attachment point is also called a chain. The chain name corresponding to the attachment point is as follows:

  • LOCAL_IN mount point: also known as INPUT chain.
  • LOCAL_OUT mount point: also known as OUTPUT chain.
  • FORWARD mount point: also known as port chain.
  • PRE_ROUTING mount point: also known as preouting chain.
  • POST_ROUTING mount point: also known as the posting chain.

Netfilter defines five constants to represent these five locations, as shown in the following code:

// File: include/linux/netfilter_ipv4.h
#define NF_IP_PRE_ROUTING   0
#define NF_IP_LOCAL_IN      1
#define NF_IP_FORWARD       2
#define NF_IP_LOCAL_OUT     3
#define NF_IP_POST_ROUTING  4

2. Register the hooks function

Register and unregister hook functions: Register the hooks

(1) Register and unregister hook functions

The kernel provides the following functions to register and cancel hook functions

// include/linux/netfilter.h
/* Function to register/unregister hook points. */

int nf_register_hook(struct nf_hook_ops *reg);
void nf_unregister_hook(struct nf_hook_ops *reg);
int nf_register_hooks(struct nf_hook_ops *reg, unsigned int n);
void nf_unregister_hooks(struct nf_hook_ops *reg, unsigned int n);

These functions are used to register custom hook operations (struct nf_hook_ops) to the specified hook node.

(2) Hook operation data structure

The structure is as follows: nf_hook_ops

struct nf_hook_ops
{
        struct list_head list;

        /* User fills in from here down. */
        nf_hookfn *hook;
        struct module *owner;
        u_int8_t pf;
        unsigned int hooknum;
        /* Hooks are ordered in ascending priority. */
        int priority;
};

This structure stores the user-defined hook function (nf_hookfn), function priority, processing protocol type (pf), hook node (hooknum) where the hook function takes effect, etc.

(3) Register hook function

After defining a hook function structure, you need to call nf_register_hook function to register it with NF_ In hooks array, NF_ register_ The hook function is implemented as follows:

// File: net/core/netfilter.c

int nf_register_hook(struct nf_hook_ops *reg)
{    struct list_head *i;
    br_write_lock_bh(BR_NETPROTO_LOCK); 
    // For nf_hooks to lock
    // The priority field indicates the priority of the hook function    
    // So find the appropriate location of the hook function through the priority field    
    
    for (i = nf_hooks[reg->pf][reg->hooknum].next; i != &nf_hooks[reg->pf][reg->hooknum];i = i->next) 
    {
        if (reg->priority < ((struct nf_hook_ops *)i)->priority)
        break;
    }
    list_add(&reg->list, i->prev); // Add hook function to linked list
    br_write_unlock_bh(BR_NETPROTO_LOCK); // For nf_hooks to unlock
    return 0;
}

nf_ register_ The implementation of hook function is relatively simple. The steps are as follows:

  • For nf_hooks is locked to protect nf_hooks variables are not subject to concurrent contention.
  • Find the correct position in the linked list of hook functions by the priority of hook functions.
  • Insert the hook function into the linked list.
  • For nf_hooks to unlock.

3. Declare hook functions

The hook function is specified by, and its function declaration is as follows: nf_hookfn *hook

// include/linux/netfilter.h

typedef unsigned int nf_hookfn(unsigned int hooknum,
                               struct sk_buff *skb,
                               const struct net_device *in,
                               const struct net_device *out,
                               int (*okfn)(struct sk_buff *));

It returns one of the following results:

// <linux/netfilter.h>
#define NF_DROP 0
#define NF_ACCEPT 1
#define NF_STOLEN 2
#define NF_QUEUE 3
#define NF_REPEAT 4
#define NF_STOP 5
#define NF_MAX_VERDICT NF_STOP

4. Processing protocol type: pf

pf (protocol family) is the identifier of the protocol family

enum {
        NFPROTO_UNSPEC =  0,
        NFPROTO_IPV4   =  2,
        NFPROTO_ARP    =  3,
        NFPROTO_BRIDGE =  7,
        NFPROTO_IPV6   = 10,
        NFPROTO_DECNET = 12,
        NFPROTO_NUMPROTO,
};

5. Hook ID: hooknum

Hook identifier. All valid identifiers of each protocol family are defined in the header file.

For example:
<linux/netfilter_ipv4.h>

/* IP Hooks */
/* After promisc drops, checksum checks. */
#define NF_IP_PRE_ROUTING       0
/* If the packet is destined for this box. */
#define NF_IP_LOCAL_IN          1
/* If the packet is destined for another interface. */
#define NF_IP_FORWARD           2
/* Packets coming from a local process. */
#define NF_IP_LOCAL_OUT         3
/* Packets about to hit the wire. */
#define NF_IP_POST_ROUTING      4
#define NF_IP_NUMHOOKS          5

6. Hook priority: priority

The priority of the hook, and all valid identifiers of each protocol family are defined in the header file.

For example:
<linux/netfilter_ipv4.h>

enum nf_ip_hook_priorities {
        NF_IP_PRI_FIRST = INT_MIN,
        NF_IP_PRI_CONNTRACK_DEFRAG = -400,
        NF_IP_PRI_RAW = -300,
        NF_IP_PRI_SELINUX_FIRST = -225,
        NF_IP_PRI_CONNTRACK = -200,
        NF_IP_PRI_MANGLE = -150,
        NF_IP_PRI_NAT_DST = -100,
        NF_IP_PRI_FILTER = 0,
        NF_IP_PRI_SECURITY = 50,
        NF_IP_PRI_NAT_SRC = 100,
        NF_IP_PRI_SELINUX_LAST = 225,
        NF_IP_PRI_CONNTRACK_CONFIRM = INT_MAX,
        NF_IP_PRI_LAST = INT_MAX,
};
enum {
        NFPROTO_UNSPEC =  0,
        NFPROTO_IPV4   =  2,
        NFPROTO_ARP    =  3,
        NFPROTO_BRIDGE =  7,
        NFPROTO_IPV6   = 10,
        NFPROTO_DECNET = 12,
        NFPROTO_NUMPROTO,
};

7. Trigger call hook function

Hook functions have been saved to different chains. When will it be triggered to call these hook functions to process packets? To trigger calls to all hook functions on a mount point (chain), you need to use NF_HOOK macro, which is defined as follows:

// File: include/linux/netfilter.h

#define   NF_HOOK(pf, hook, skb, indev, outdev, okfn)  (list_empty(&nf_hooks[(pf)][(hook)]) ? (okfn)(skb) : nf_hook_slow((pf), (hook), (skb), (indev), (outdev), (okfn)))

First, let's introduce NF_ Functions of each parameter of hook macro:

  • pf: protocol type, which is NF_ The first dimension of hooks array, such as IPv4 protocol, is PF_INET.
  • Hook: which chain (mount point) hook function to call, such as NF_IP_PRE_ROUTING.
  • indev: the device object that receives the packet.
  • outdev: the device object that sends the packet.
  • okfn: when all hook functions on the chain are processed, this function will be called to continue processing the packet.

And NF_ The implementation of hook macro is also relatively simple. First, judge whether the linked list of hook functions is empty. If it is empty, directly call okfn function to process data packets, otherwise call NF_HOOK_ The slow function is used to process packets. Let's take a look at NF_HOOK_ Implementation of slow function:

// File: net/core/netfilter.c

int nf_hook_slow(int pf, unsigned int hook, struct sk_buff *skb,
                 struct net_device *indev, struct net_device *outdev,
                 int (*okfn)(struct sk_buff *))
{
    struct list_head *elem;
    unsigned int verdict;
    int ret = 0;

    elem = &nf_hooks[pf][hook]; // Get the linked list of hook functions to call

    // Traverse the linked list of hook functions, and call the hook function to process the data packet
    verdict = nf_iterate(&nf_hooks[pf][hook], &skb, hook, indev, outdev, &elem, okfn);
    ...
    // If the processing result is NF_ACCEPT means that the packet is processed through all hook functions, then call okfn function to continue processing the packet
    // If the processing result is NF_DROP indicates that the packet is rejected and should be discarded
    switch (verdict) {
    case NF_ACCEPT:
        ret = okfn(skb);
        break;
    case NF_DROP:
        kfree_skb(skb);
        ret = -EPERM;
        break;
    }

    return ret;
}

nf_ hook_ The implementation of slow function is also relatively simple. The process is as follows:

  • Call NF first_ Iterate function to traverse the linked list of hook functions, and call the hook function on the linked list to process data packets.
  • If the processing result is NF_ACCEPT means that the data packet is processed through all hook functions, then call okfn function to continue processing the data packet.
  • If the processing result is NF_DROP indicates that the packet has not been processed by the hook function and should be discarded.

Since Netfilter calls NF_HOOK macro to call the hook function on the linked list of hook functions, so where does the kernel call this macro?

For example, the processing function IP of the packet entering the IPv4 protocol layer_ NF is called in the RCV function_ Hook macro to process data packets. The code is as follows:

// File: net/ipv4/ip_input.c

int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt)
{
    ...
    return NF_HOOK(PF_INET, NF_IP_PRE_ROUTING, skb, dev, NULL, ip_rcv_finish);
}

As shown in the above code, in IP_ The NF_ is called in the RCV function. Hook macro is used to process the input packet, and the hook function chain (mount point) it calls is NF_IP_PRE_ROUTING. okfn is set to ip_rcv_finish, that is, when NF_ IP_ PRE_ After all hook functions on the routing chain successfully process the packet, IP will be called_ rcv_ Finish function to continue processing the packet.

3, Netfilter application case

The following is a Demo of a kernel module found on the network. The basic function of this module is to pass through IPv4 network layer NF_ INET_ LOCAL_ Print the source Mac address, destination Mac address, source IP and destination IP of the packet of the in node, Source package download.NF_INET_LOCAL_IN

The code is as follows:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/skbuff.h>
#include <linux/ip.h>
#include <linux/udp.h>
#include <linux/tcp.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>


MODULE_LICENSE("GPLv3");
MODULE_AUTHOR("SHI");
MODULE_DESCRIPTION("Netfliter test");

static unsigned int
nf_test_in_hook(unsigned int hook, struct sk_buff *skb, const struct net_device *in,
                const struct net_device *out, int (*okfn)(struct sk_buff*));

static struct nf_hook_ops nf_test_ops[] __read_mostly = {
  {
    .hook = nf_test_in_hook,
    .owner = THIS_MODULE,
    .pf = NFPROTO_IPV4,
    .hooknum = NF_INET_LOCAL_IN,
    .priority = NF_IP_PRI_FIRST,
  },
};

void hdr_dump(struct ethhdr *ehdr) {
    printk("[MAC_DES:%x,%x,%x,%x,%x,%x" 
           "MAC_SRC: %x,%x,%x,%x,%x,%x Prot:%x]\n",
           ehdr->h_dest[0],ehdr->h_dest[1],ehdr->h_dest[2],ehdr->h_dest[3],
           ehdr->h_dest[4],ehdr->h_dest[5],ehdr->h_source[0],ehdr->h_source[1],
           ehdr->h_source[2],ehdr->h_source[3],ehdr->h_source[4],
           ehdr->h_source[5],ehdr->h_proto);
}

#define NIPQUAD(addr) \
        ((unsigned char *)&addr)[0], \
        ((unsigned char *)&addr)[1], \
        ((unsigned char *)&addr)[2], \
        ((unsigned char *)&addr)[3]
#define NIPQUAD_FMT "%u.%u.%u.%u"

static unsigned int
nf_test_in_hook(unsigned int hook, struct sk_buff *skb, const struct net_device *in,
                const struct net_device *out, int (*okfn)(struct sk_buff*)) {
  struct ethhdr *eth_header;
  struct iphdr *ip_header;
  eth_header = (struct ethhdr *)(skb_mac_header(skb));
  ip_header = (struct iphdr *)(skb_network_header(skb));
  hdr_dump(eth_header);
  printk("src IP:'"NIPQUAD_FMT"', dst IP:'"NIPQUAD_FMT"' \n",
         NIPQUAD(ip_header->saddr), NIPQUAD(ip_header->daddr));
  return NF_ACCEPT;
}

static int __init init_nf_test(void) {
  int ret;
  ret = nf_register_hooks(nf_test_ops, ARRAY_SIZE(nf_test_ops));
  if (ret < 0) {
    printk("register nf hook fail\n");
    return ret;
  }
  printk(KERN_NOTICE "register nf test hook\n");
  return 0;
}

static void __exit exit_nf_test(void) {
  nf_unregister_hooks(nf_test_ops, ARRAY_SIZE(nf_test_ops));
}

module_init(init_nf_test);
module_exit(exit_nf_test);

Results after dmesg | tail:

[452013.507230] [MAC_DES:70,f3,95,e,42,faMAC_SRC: 0,f,fe,f6,7c,13 Prot:8]
[452013.507237] src IP:'10.6.124.55', dst IP:'10.6.124.54' 
[452013.944960] [MAC_DES:70,f3,95,e,42,faMAC_SRC: 0,f,fe,f6,7c,13 Prot:8]
[452013.944968] src IP:'10.6.124.55', dst IP:'10.6.124.54' 
[452014.960934] [MAC_DES:70,f3,95,e,42,faMAC_SRC: 0,f,fe,f6,7c,13 Prot:8]
[452014.960941] src IP:'10.6.124.55', dst IP:'10.6.124.54' 
[452015.476335] [MAC_DES:70,f3,95,e,42,faMAC_SRC: 0,f,fe,f6,7c,13 Prot:8]
[452015.476342] src IP:'10.6.124.55', dst IP:'10.6.124.54' 
[452016.023311] [MAC_DES:70,f3,95,e,42,faMAC_SRC: 0,f,fe,f6,7c,13 Prot:8]
[452016.023318] src IP:'10.6.124.55', dst IP:'10.6.124.54'

This Demo program is a kernel module, and the module entry is module_init incoming init_nf_test function.

In init_ nf_ In the test function, it passes the NF provided by Netfilter_ register_ The hooks interface will customize nf_test_opt is registered in the hook node. nf_test_opt is struct nf_hook_ops type structure array, which contains all key elements, such as the registration node of hook function (NF_INET_LOCAL_IN here) and hook function (nf_test_in_hook).

In NF_ test_ in_ Inside the hook function, it checks each passed packet and prints its source Mac address, destination Mac address, source IP address and destination IP address. Finally, NF is returned_ Accept, the packet is sent to the next hook function for processing.

4, Linux flow control

Traffic Control HOWTO: Netfilter is mostly used to realize flow control
The more detailed documents are Linux Advanced Routing & Traffic Control HOWTO And reduced version Traffic Control HOWTO.

5, Extended reading

Monitoring and Tuning the Linux Networking Stack: Sending Data

Linux Netfilter and Traffic Control

Netfilter and iptables homepage

Illustration of Linux network packet sending process

Network foundation - seven layer model

OSI seven layer model and TCP/IP five layer model

Analysis on the receiving and contracting process of Linux network layer and Netfilter framework

Netfilter & iptables principle

Implementation of Netfiler & iptables (I) - Implementation of Netfilter

Posted by groberts23 on Mon, 20 Sep 2021 23:15:11 -0700