您的位置:首页 > 运维架构 > Linux

Linux Kernel 学习笔记10:hook函数

2017-06-01 10:03 1321 查看
(本章基于:Linux-4.4.0-37)

linux 内核中有一套hook函数机制,可在不同hook点位置监控网络数据包,并执行丢弃、修改等操作。网络防火墙就是通过此机制实现的。

注册注销hook函数:


linux/netfilter.h

注册钩子函数:

int nf_register_hook(struct nf_hook_ops *reg);

注销:

void nf_unregister_hook(struct nf_hook_ops *reg);

struct nf_hook_ops数据结构:
struct nf_hook_ops {
struct list_head 	list;

/* User fills in from here down. */
nf_hookfn		*hook;			//hook处理函数
struct net_device	*dev;
void			*priv;
u_int8_t		pf;			//协议类型
unsigned int		hooknum;		//hook点
/* Hooks are ordered in ascending priority. */
int			priority;		//优先级
};

list:链表结构,被注册的nf_hook_ops实际是以链表节点的形式存储在nf_hooks中的,nf_hooks是一个二维数组,后面详细介绍;

hook:hook处理函数,当接收到一个数据包后,调用此函数进行处理;

pf:协议类型,ipv4就选PF_INET;

hooknum:hook点,linux 网络协议栈中包含5个hook,在不同的情况下对数据包进行监控,后面详细介绍;

priority:优先级(越小优先级越高)

HOOK点:
linux网络协议栈中有5个hook点:

之间的关系如下图:



PRE_ROUTING: 所有进入协议栈的数据包都会经过PRE_ROUTING;

LOCAL_IN:经过路由判决,确认是发给本机的数据包会经过LOCAL_IN;

FORWARD:若经路由判决不是发送本机,而且经本机转发的报文会经过FORWARD;

LOCAL_OUT:由本机进程发送出去数据包经过LOCAL_OUT;

POST_ROUTING:所有数据包在离开本机前会经过POST_ROUTING;

linux/netfilter_bridge.h中定义了相关hook点:
/* Bridge Hooks */
/* After promisc drops, checksum checks. */
#define NF_BR_PRE_ROUTING	0
/* If the packet is destined for this box. */
#define NF_BR_LOCAL_IN		1
/* If the packet is destined for another interface. */
#define NF_BR_FORWARD		2
/* Packets coming from a local process. */
#define NF_BR_LOCAL_OUT		3
/* Packets about to hit the wire. */
#define NF_BR_POST_ROUTING	4

nf_hooks结构:
struct list_head nf_hooks[NFPROTO_NUMPROTO][NF_MAX_HOOKS];

这是一个二维数组,NFPROTO_NUMPROTO表示相关协议,NF_MAX_HOOKS表示hook点位置。被注册的hook操作节点按照优先级接在在这个二位数据后面,如下图:



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_HELPER = 300,
NF_IP_PRI_CONNTRACK_CONFIRM = INT_MAX,
NF_IP_PRI_LAST = INT_MAX,
};

回调函数:

回调函数结构如下:
unsigned int nf_hookfn(void *priv,
struct sk_buff *skb,
const struct nf_hook_state *state);
每监控到一条数据包,就会调用一次次函数,数据包信息存储在sk_buff结构中,参考sk_buff相关介绍。这个函数的返回值决定了函数结束后此数据包的走向,有如下几种返回值:

1. NF_ACCEPT继续正常传输数据报。这个返回值告诉Netfilter:到目前为止,该数据包还是被接受的并且该数据包应当被递交到网络协议栈的下一个阶段。

2. NF_DROP丢弃该数据报,不再传输。

3. NF_STOLEN模块接管该数据报,告诉Netfilter“忘掉”该数据报。该回调函数将从此开始对数据包的处理,并且Netfilter应当放弃对该数据包做任何的处理。但是,这并不意味着该数据包的资源已经被释放。这个数据包以及它独自的sk_buff数据结构仍然有效,只是回调函数从Netfilter获取了该数据包的所有权。

4. NF_QUEUE对该数据报进行排队(通常用于将数据报给用户空间的进程进行处理)

5. NF_REPEAT 再次调用该回调函数,应当谨慎使用这个值,以免造成死循环。

例:

此例程用于捕捉来自192.168.31.4的ping包:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/netfilter.h>
#include <linux/netfilter_bridge.h>
#include <linux/netfilter_ipv4.h>
#include <linux/ip.h>

static void
IP2Str(char *ipaddr, int size, uint32_t ip)
{
snprintf(ipaddr, size, "%d.%d.%d.%d", ( ip >> 24 ) & 0xff
, ( ip >> 16 ) & 0xff
, ( ip >> 8 ) & 0xff
, ip & 0xff);
}

unsigned int
my_hook_fun(void *priv, struct sk_buff *skb, const struct nf_hook_state *state)
{
struct iphdr *iph;
char ipaddr[17];

if( unlikely(!skb) ) {
return NF_ACCEPT;
}

iph = ip_hdr(skb);
if( unlikely(!iph) ) {
return NF_ACCEPT;
}

if( likely(iph->protocol != IPPROTO_ICMP) ) {
return NF_ACCEPT;
}

memset(ipaddr, 0, sizeof(ipaddr));
IP2Str(ipaddr, sizeof(ipaddr), ntohl(iph->saddr));
if( strcmp(ipaddr, "192.168.31.4") == 0 ) {
printk(KERN_INFO "receive ping from 192.168.31.4\n");
}

return NF_ACCEPT;
}

static struct nf_hook_ops my_hook_ops = {
.hook           = my_hook_fun,          //hook处理函数
.pf             = PF_INET,              //协议类型
.hooknum        = NF_BR_PRE_ROUTING,    //hook注册点
.priority       = NF_IP_PRI_FIRST,      //优先级
};

static void
hello_cleanup(void)
{
nf_unregister_hook(&my_hook_ops);
}

static __init int hello_init(void)
{

if ( nf_register_hook(&my_hook_ops) != 0 ) {
printk(KERN_WARNING "register hook error!\n");
goto err;
}
printk(KERN_ALERT "hello init success!\n");
return 0;

err:
hello_cleanup();
return -1;
}

static __exit void hello_exit(void)
{
hello_cleanup();
printk(KERN_WARNING "helloworld exit!\n");
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Stone");
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息