热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

ebtableshook

1 概述netfliter框架不仅仅在ipv4中有应用,bridge,ipv4,ipv6,decnet 这四种协议中都有应用,其中ipv4中又分开了arp和ip的两种其实netfl
1 概述

netfliter框架不仅仅在ipv4中有应用,bridge,ipv4,ipv6,decnet 这四种协议中都有应用,其中ipv4中又分开了arp和ip的两种

其实netfliter是个大的框架,在ipv4中对应的应用层工具是iptables,在bridge中对应的应用层工具是ebtables,在arp中对应的应用层工具是arptables

iptables 中有raw,filter,nat,mangle,security,5个table,

ebtables 中有broute,filter,nat,3个table,

arptables 中有filter,1个table

具体的可以查看源码目录linux/net/目录下的ipv4,ipv6,decnet,bridge目录下的netfilter

2 一些概念

2.1 三层hook函数的优先级

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,

};

2.2 二层hook 函数的优先级

enum nf_br_hook_priorities {

        NF_BR_PRI_FIRST = INT_MIN,

        NF_BR_PRI_NAT_DST_BRIDGED = -300,

        NF_BR_PRI_FILTER_BRIDGED = -200,

        NF_BR_PRI_BRNF = 0,        

        NF_BR_PRI_NAT_DST_OTHER = 100,

        NF_BR_PRI_FILTER_OTHER = 200,

        NF_BR_PRI_NAT_SRC = 300,

        NF_BR_PRI_LAST = INT_MAX,

}

2.3 hook点,hooknum,hook函数

三层(ip)有5个hooknum,分别是pre_routing,local_in,forward,local_out,post_routing

二层(bridge)有6个hooknum,分别是

pre_routing,local_in,forward,local_out,post_routing,brouting,

在头文件./uapi/linux/netfilter_bridge.h ./uapi/linux/netfilter_ipv4.h 可看到

linux/net/netfilter 是整个netfilter框架的代码,不同的协议下面的netfilter是调用的代码

hook函数,就是我们自定义的那些函数,函数优先级,数值越大的,优先级越小

一个hook点是由协议和hooknum两者决定的,nf_hooks[pf][hooknum],因此,协议不一样,hooknum一样也是不一样的hook点的,ipv4的协议是NFPROTO_INET,bridge的协议是NFPROTO_BRIDGE,而只有同一个hook点的函数才会有优先级的问题。因此,在正常情况下,同一个数据包在某一层中只会遍历某一种协议的hook点,是一个水平分层的问题,虽然都注册在netfilter框架下,可是协议决定了这是一个水平的流程。当数据包上到另外一层那就是另外一层的水平。

但是有一些地方在三层的改变会影响二层的结构的,比如像ip-DNAT的,改变了三层的daddr,那么对应的二层的dmac地址也是会跟着改变的,那么这个应该在routing之前还是应该在brigding之前做呢?按道理虽然改的是三层的内容,但是这个应该在brigding之前做的,这样在二层选择出口的时候,才不会错。所以其实二层中有些地方是有穿插三层的hook点的调用的,所以整个结构看起来才会不那么清晰(后面的函数分析会证实这个想法)

hooknum 和pf 决定了hook点,hook点上面有hook函数,根据优先级来进行hook函数的调用。

NF_HOOK 这个宏就是遍历给定的hook点(nf_hooks[pf][hooknum])上面的所有hook函数

在整个网络协议栈(包括二层的)上面的不同位置的NF_HOOK的作用就是遍历不同的hook点上hook函数,这就是netfilter做的事情

3 数据包在网桥的流转

3.1 接收入口函数

netif_rx

netif_receive_skb(skb)-->netif_receive_skb_internal()->__netif_receive_skb()-> __netif_receive_skb_core()

netif_rx 是上层处理函数中最接近驱动层的函数,往queue里面放skb

netif_receive_skb 是最接近上层处理函数的入口函数,在软中断中执行,在queue中取完skb后的处理函数

netif_rx 和netif_receive_skb的关系还没有搞的很明白,两者没有明显的调用关系,在驱动中两者都有调用,

 __netif_receive_skb_core 是真正处理skb的函数,到底接着数据包是怎么走的,在这里判断的

对于网桥的数据包,就是rx_handler = br_handle_frame,在调用这个函数之前已经调用了skb_vlan_untag把二层头包含vlan信息的部分去掉,

并且把vlan信息记录在skb->vlan_proto(协议),和skb->vlan_tci(优先级和id)

即bridge的入口函数是br_handle_frame,在br_input.c

br_handle_frame 主要有两个分支有NF_HOOK的调用的,如下: 

|---link-local----      NF_HOOK(NFPROTO_BRIDGE,NF_BR_LOCAL_IN,..,br_handle_local_finish) 
|---forward--          NF_HOOK(NFPROTO_BRIDGE, NF_BR_PRE_ROUTING, ...,br_handle_frame_finish)

link-local :dmac是本地链路地址。至于什么是本地链路地址,可以google,只知道在ipv6中(fe80)用得比较多,其他没什么了解

br_handle_frame_finish 这个函数对数据包的dmac进行判断,然后走不同的处理函数.

dmac 的不同的,处理方式不同:

A.bridge it,如果dmac是在网桥的别的端口,复制一份帧到dmac所在的端口                    ---->br_forward 

B.flood it over all the forwarding bridge ports,如果dmac地址是网桥不知道的,就泛洪     ---->br_flood_forward

C.pass it to the higher protocol code,如果dmac是网桥的,或者网桥其中一个端口的        ---->br_pass_frame_up

D.ignore it,dmac在进来的端口的这一边的,即dmac能在进来端口的mac地址表中找到             ---->br_forward

3.2 转发

br_forward,通过should_deliver()来进行判断,是否真的需要__br_forward 还是 ignore it,

__br_forward->NF_HOOK(NFPROTO_BRIDGE, NF_BR_FORWARD, ... skb->dev,br_forward_finish) ,

__br_forward 函数改变了skb->dev

br_forward_finish->NF_HOOK(NFPROTO_BRIDGE,NF_BR_POST_ROUTING,skb,NULL,skb->dev,br_dev_queue_push_xmit);

br_dev_queue_push_xmit->dev_queue_xmit

br_flood_forward->br_flood(br, skb, skb2, __br_forward, unicast)->__br_forward 

same as __br_forward

3.3 local_in

br_pass_frame_up->NF_HOOK(NFPROTO_BRIDGE, NF_BR_LOCAL_IN, skb, indev,NULL,netif_receive_skb)

3.4 发送入口函数

对于二层以上的层,只有网桥这个接口,没有其绑定的ethx了(通过路由表可知),网桥的发送函数是br_dev_xmit

在br_dev_xmit 也会根据dmac判断是进行br_multicast_deliver,br_deliver,

还是br_flood_delver,但是最后调用都是__br_deliver

__br_deliver-> NF_HOOK(NFPROTO_BRIDGE, NF_BR_LOCAL_OUT, skb, NULL,skb->dev,br_forward_finish)

br_forward_finish->NF_HOOK(NFPROTO_BRIDGE,NF_BR_POST_ROUTING,skb,NULL,skb->dev,br_dev_queue_push_xmit);

br_dev_queue_push_xmit->dev_queue_xmit

3.5 结论

根据上面的分析,通过网桥进来的数据包会经过的hook点跟在三层的是一样的

本地的会经过pre_routing 和local_in, 转发的会经过pre_routing,forward,post_routing ,
而本地出去的会经过local_out,post_routing

4 二层调用三层的hook函数的实现

4.1 NF_HOOK 和NF_HOOK_THRESH的区别

NF_HOOK 封装了NF_HOOK_THRESH ,是特殊的NF_HOOK_THRESH, 是从优先级最高的hook函数开始的

NF_HOOK_THRESH,

static inline int NF_HOOK{

         return NF_HOOK_THRESH(pf, hook, skb, in, out, okfn, INT_MIN)

4.2 br_netfilter.c分析

二层hook点中调用三层的hook的实现主要在linux/net/bridge/br_netfilter.c ,这个函数注册了7个hook函数,其中5个是NFPROTO_BRIDGE协议的,2个分别是NFPROTO_IPV4,NFPROTO_IPV6的

NFPROTO_BRIDGE的5个函数分别是br_nf_pre_routing,br_nf_local_in,br_nf_forward_ip,

br_nf_forward_arp,br_nf_post_routing的,br_nf_forward_ip 优先级是 -1,其他优先级都是0,

NFPROTO_IPV4/6 的两个都是在pre_routing hook点,优先级是first,hook函数都是ip_sabotage_in,这个函数的作用就是防止多次调用三层pre_routing hook点的hook函数

因此目前看到的在NFPROTO_BRIDGE协议下系统注册了的钩子函数的顺序如下:

pre_routing  ebt_nat_in(dnat)->br_nf_pre_routing

local_in     ebt_in_hook(filter)->br_nf_local_in

forward      ebt_in_hook(filter)->br_nf_forward_ip->br_nf_forward_arp

local_out     ebt_nat_out(dnat_other)->ebt_out_hook(filter_other)

post_routing  ebt_nat_out(snat)->br_nf_post_routing(last)

(1). br_nf_pre_routing->NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, skb, skb->dev,NULL,br_nf_pre_routing_finish)

br_nf_pre_routing_finish->NF_HOOK_THRESH(NFPROTO_BRIDGE, NF_BR_PRE_ROUTING, skb,skb->dev, NULL,br_handle_frame_finish, 1);

到br_handle_frame_finish 就走完了pre_routing的钩子了,其实NF_HOOK_THRESH 就是为了走完pre_routing 优先级大于1的钩子函数

正常的数据包走br_hadnle_frame 进来调用了一次NF_HOOK ,执行NFPROTO_BRIDGE的pre_routing的hook点中的hook函数,当执行到      br_nf_pre_routing这个钩子函数的时候,会先去调用一次三层的pre_routing的所有hook函数,然后再回到br_nf_pre_routing_finish

因为在br_nf_pre_routing 中返回值是NF_STOLEN,所以在br_handle_frame调用的

NF_HOOK(NFPROTO_BRIDGE, NF_BR_PRE_ROUTING, ...,br_handle_frame_finish),

到br_nf_pre_routing 就结束了,所以会有在br_nf_pre_routing_finish->NF_HOOK_THRESH()的过程,是为了重新接上pre_routing 后面的hook函数

有了这个函数br_nf_pre_routing,就可以对只经过二层的数据包做三层的dnat,

(2).br_nf_local_in->nothing ,

(3).br_nf_forward_ip->NF_HOOK(pf, NF_INET_FORWARD, skb, brnf_get_logical_dev(skb, in),parent,br_nf_forward_finish),pf=INET/INET6

bf_nf_forward_finish->NF_HOOK_THRESH(NFPROTO_BRIDGE,NF_BR_FORWARD,skb, in,skb->dev, br_forward_finish, 1);

这里主要是经过了3层的forward hook点,就是经过二层走的数据包可以在三层的forward链做过滤,主要是结合physdev模块做indev和outdev的过滤。继续NF_HOOK_THRESH的时候,会走到优先级是1的hook函数那里,跳过了br_nf_forward_arp,因为一个skb->protocol,只能是一种,不可能既是ip,也是arp,既然在br_nf_forward_ip中能走到br_nf_forward_finish就证明这是个ip包了,如果不是ip包,在一开始就会返回NF_ACCEPT,让其继续走原来的遍历顺序

br_nf_forward_arp->NF_HOOK(NFPROTO_ARP,NF_ARP_FORWARD,skb, (struct net_device*)in,(struct net_device *)out,br_nf_forward_finish);

这个就在ARP的forward链上做过滤

注意两个NF_HOOK中传进去的indev和outdev 的区别,不一样的

(4).br_nf_post_routing->NF_HOOK(pf, NF_INET_POST_ROUTING, skb, NULL,realoutdev,br_nf_dev_queue_xmit)

注意br_nf_post_routing 的优先级是last,

在post_routing中也先判断,数据包是否是经过bridge的了,如果是从

ip/local_out->bridge/local_out,或者直接bridge/local_out的数据包都没有必要再经过一次ip/post_routing,即只有经过bridge转发的包,
才需要经过ip/post_routing

4.3 防止多次调用三层hook点的hook函数

ip_sabotage_in 在NFPROTO_IPV4/6的pre_routing 的first,如果是从网桥上来到三层的数据包,其实三层的pre_routing已经做过了,这个函数

就是控制如果是从网桥上来的数据包就返回NF_STOP ,停止这个hook点的后续hook函数的检查,并且接受数据包(防止两次走过三层的pre_routing),如果不是从网桥上来的包,就返回NF_ACCEP ,继续做这个hook点的hook函数的检查的

根据4.2 可知,在二层只有pre_routing,forward,post_routing 三个hook点会调用到三层对应hook点的hook函数,而只有经过
bridge/pre_routing->bridge/local_in->ip/pre_routing这样路径进来的数据包才需要在ip/pre_routing的位置判断是否是网桥上来的包,如果是网桥上来的就不再需要遍历这个hook点剩下的hook函数了.其他的路径,都不可能同时经过二层和三层的同一个hook点,

所以只需要在ip/pre_routing的first的位置注册ip_sabotage_in,就可以了,ip/forward,ip/post_routing 都不需要

5 brouting hook点

brouting的调用不是通过NF_HOOK 这种传统的方式来进行的,而且系统没有通过nf_register_hooks 这种方式注册对应的hook函数,

只是把一个函数赋值给了一个在br_input.c 中定义的br_should_route_hook_t

 *br_should_route_hook 这个变量

然后通过这个变量来进行函数的调用,真正的函数是net/bridge/netfilter/ebtable_broute.c 中的ebt_broute

ebtable 有三个表,分别是

broute:系统没有注册有hook函数,允许注册的hook点只有一个就是brouting

nat:pre_routing(dnat),post_routing(snat),local_out(dnat_other)

filter:local_in,forward,local_out(other)

STP 最小生成树协议的5中状态

#define BR_STATE_DISABLED 0           

#define BR_STATE_LISTENING 1          

#define BR_STATE_LEARNING 2          

#define BR_STATE_FORWARDING 3        

#define BR_STATE_BLOCKING 4

DISABLE:   什么功能都没有,只有一个逻辑设备。

LISTENING: 可以接收和发送网络传输的BPDU,包括Configureation BPDU和TCN BPDU,但不能进行数据帧的转发、不能学习。

LEARNING:  可以接收和发送BPDU,可以学习,但是不能进行数据帧的转发。

FORWARDING:可以接收和发送BPDU、可以学习、可以进行数据帧的转发。

BLOCKING:  只能接收BPDU,不能发送BPDU,不能学习,不能转发数据帧。

至于什么是BPDU 这个可以去看看linux-bridge的最小生成树的相关知识

在br_handle_frame函数的forward 标签下,

如果p->state 是FORWARDING的才会调用到brouting的hook点的唯一的hook函数ebt_broute,这个是在pre_routing 的调用之前的,
这里是以调用函数的方式来做ebtable的规则的,而不是遍历hook点上面的hook函数来做ebtables上面的规则的,因此,如果想自定义
hook函数,估计要改源码,即brouting这个hook点,只提供了用户接口,没有提供开发接口

至于p->state (端口状态)是什么时候进行状态转换的?还不清楚

网卡新建为一个网桥的端口的时候状态是BR_STATE_DISABLED,

6 结论

经过二层的数据包会经过的hook点如下:
技术分享
不知道怎样把大图弄上CU,只能用viso画了,然后截图上去了,有点模糊

还有一篇从ebtables的使用角度分析的文章,个人感觉不错的,也贴在这里了
http://ebtables.netfilter.org/br_fw_ia/br_fw_ia.html

ebtables hook


推荐阅读
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
  • HDU 2372 El Dorado(DP)的最长上升子序列长度求解方法
    本文介绍了解决HDU 2372 El Dorado问题的一种动态规划方法,通过循环k的方式求解最长上升子序列的长度。具体实现过程包括初始化dp数组、读取数列、计算最长上升子序列长度等步骤。 ... [详细]
  • 本文介绍了C#中数据集DataSet对象的使用及相关方法详解,包括DataSet对象的概述、与数据关系对象的互联、Rows集合和Columns集合的组成,以及DataSet对象常用的方法之一——Merge方法的使用。通过本文的阅读,读者可以了解到DataSet对象在C#中的重要性和使用方法。 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • 本文介绍了在SpringBoot中集成thymeleaf前端模版的配置步骤,包括在application.properties配置文件中添加thymeleaf的配置信息,引入thymeleaf的jar包,以及创建PageController并添加index方法。 ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • 后台获取视图对应的字符串
    1.帮助类后台获取视图对应的字符串publicclassViewHelper{将View输出为字符串(注:不会执行对应的ac ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • Mac OS 升级到11.2.2 Eclipse打不开了,报错Failed to create the Java Virtual Machine
    本文介绍了在Mac OS升级到11.2.2版本后,使用Eclipse打开时出现报错Failed to create the Java Virtual Machine的问题,并提供了解决方法。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • 知识图谱——机器大脑中的知识库
    本文介绍了知识图谱在机器大脑中的应用,以及搜索引擎在知识图谱方面的发展。以谷歌知识图谱为例,说明了知识图谱的智能化特点。通过搜索引擎用户可以获取更加智能化的答案,如搜索关键词"Marie Curie",会得到居里夫人的详细信息以及与之相关的历史人物。知识图谱的出现引起了搜索引擎行业的变革,不仅美国的微软必应,中国的百度、搜狗等搜索引擎公司也纷纷推出了自己的知识图谱。 ... [详细]
  • 本文介绍了通过ABAP开发往外网发邮件的需求,并提供了配置和代码整理的资料。其中包括了配置SAP邮件服务器的步骤和ABAP写发送邮件代码的过程。通过RZ10配置参数和icm/server_port_1的设定,可以实现向Sap User和外部邮件发送邮件的功能。希望对需要的开发人员有帮助。摘要长度:184字。 ... [详细]
  • 动态规划算法的基本步骤及最长递增子序列问题详解
    本文详细介绍了动态规划算法的基本步骤,包括划分阶段、选择状态、决策和状态转移方程,并以最长递增子序列问题为例进行了详细解析。动态规划算法的有效性依赖于问题本身所具有的最优子结构性质和子问题重叠性质。通过将子问题的解保存在一个表中,在以后尽可能多地利用这些子问题的解,从而提高算法的效率。 ... [详细]
author-avatar
手机用户2602880641
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有