跳到主要内容

Netfilter

Netfilter 是Linux 2.4 内核引入的一个子系统,它作为一个通用的、抽象的框架,提供一整套的hook函数的管理机制,使得诸如数据包过滤、网络地址转换(NAT)和基于协议类型的连接跟踪成为了可能。 netfilter的架构就是在整个网络流程的若干位置放置了一些检测点(HOOK),而在每个检测点上登记了一些处理函数进行处理。

  • Iptables的功能实现就是在Netfilter之上完成的。
  • 流控的实现也是基于netfilter
  • Linux Virtual Server(LVS) 基于netfilter

内核版本 4.13 之前 使用 nf_register_hook , nf_unregister_hook

内核版本 4.13 之后 使用 ‌nf_register_net_hook,nf_unregister_net_hook

相关结构介绍

结构体

  • pf 数据包的协议族
  • hooknum 挂载点的位置
  • priority 钩子的优先级
struct nf_hook_ops
{
struct list_head list;
/* User fills in from here down. */
nf_hookfn *hook;
struct module *owner;
int pf;
int hooknum;
/* Hooks are ordered in ascending priority. */
int priority;
};

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,
}
//IPv4钩子点的定义如下:
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
};

4.13内核版本分界

  • priv:私有数据
  • skb:正在处理的报文
  • state:将相关参数都将存储到 state 中
  • hook:hook 函数
  • dev:设备
  • pf:协议族
  • hooknum:hook 触发点的编号
  • priority:优先级
  • net_device *in:用于描述数据包到达的接口
  • net_device *out:用于描述数据包离开的接口
#define NF_IP_PRE_ROUTING   0   // 在进行完整性检查之后,可以截获接收的所有报文,包括目的地址是自己的报文和需要转发的报文;目的IP地址转换在此点
#define NF_IP_LOCAL_IN 1 // 路由决策后,可以截获目的地址是自己的报文,INPUT 包过滤在这里进行
#define NF_IP_FORWARD 2 // 截获所有转发的报文,FORWARD 在这里进行过滤
#define NF_IP_LOCAL_OUT 3 // 可以截获自身发出的所有报文(不包括转发),OUTPUT 过滤在这里进行
#define NF_IP_POST_ROUTING 4 // 可以截获发送的所有报文,包括自身发出的报文和转发的报文

#define NF_DROP 0 // 丢弃数据包,不在继续
#define NF_ACCEPT 1 // 正常传输报文 下一个 HOOK 函数可以接着处理了
#define NF_STOLEN 2 // Netfilter 模块接管该报文,不再继续传输
#define NF_QUEUE 3 // 对该数据报进行排队,通常用于将数据报提交给用户空间进程处理
#define NF_REPEAT 4 // 再次调用该钩子函数
#define NF_STOP 5 // 继续正常传输报文 后面的 HOOK 函数你们就不要处理了


typedef unsigned int nf_hookfn(void *priv,struct sk_buff *skb,const struct nf_hook_state *state);

struct nf_hook_ops {
nf_hookfn *hook;
struct net_device *dev;
void *priv;
u_int8_t pf;
unsigned int hooknum;
int priority;
};

struct nf_hook_state {
unsigned int hook;
u_int8_t pf;
struct net_device *in;
struct net_device *out;
struct sock *sk;
struct net *net;
int (*okfn)(struct net *, struct sock *, struct sk_buff *);
};

参数 in 只用于NF_IP_PRE_ROUTING和NF_IP_LOCAL_IN,参数out只用于NF_IP_LOCAL_OUT和NF_IP_POST_ROUTING

实战(4.13之后)

因为制作 hook 触发需要将程序加载到内核中,所以先了解下 linux 内核模块化,加载和卸载。

main.c

#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 int __init init(void) {
printk(KERN_INFO "register new kernel module\n");
return 0;
}

static void __exit exit(void) {

}

module_init(init);
module_exit(exit);

makefile

KERNEL_DIR=/usr/src/linux-headers-5.4.0-192-generic
obj-m = nf_test.o
nf_test-objs = main.o

all:
make -C $(KERNEL_DIR) M=$(PWD) modules
clean:
make -C $(KERNEL_DIR) M=$(PWD) clean

install:
sudo insmod nf_test.ko
remove:
sudo rmmod nf_test.ko
  • make
  • make install
  • make remove
  • dmesg

注册了hook的main.c

#include <linux/module.h> 
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/kthread.h>
#include <linux/delay.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>
#include <linux/netdevice.h>

static char *if_name = "eth0";

static unsigned int
nf_test_in_hook_413(void * priv, struct sk_buff *skb ,const struct nf_hook_state* state){

if(state->out != NULL && strcmp(state->out->name,if_name) == 0)
{
printk(KERN_INFO "nf_test_in_hook_413\n");
return NF_ACCEPT;
}
else
{
return NF_ACCEPT;
}
return NF_ACCEPT;

}
static struct nf_hook_ops nf_drop={
.hook = nf_test_in_hook_413,
.pf = PF_INET,
.hooknum = NF_INET_LOCAL_OUT,
.priority = NF_IP_PRI_FIRST,
};
static int __init hello_world_init(void) {
printk(KERN_INFO "Hello, world!\n");
nf_register_net_hook(&init_net,&nf_drop);
return 0;
}

// 模块卸载函数
static void __exit hello_world_exit(void) {
nf_unregister_net_hook(&init_net,&nf_drop);
printk(KERN_INFO "Goodbye\n");
}

module_init(hello_world_init); // 宏用于加载模块
module_exit(hello_world_exit); // 宏用于卸载模块

MODULE_LICENSE("GPL");
MODULE_AUTHOR("lianghao");
MODULE_DESCRIPTION("nftest");
MODULE_VERSION("1.0");