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");