LInux下桥接模式详解三
上篇文章介绍了Linux内核桥接模式涉及到的几个结构,本节就重点放在数据包的处理上!
本节所有代码参考LInux 3.10.1内核!
前面已经提到一个数据包从网卡流到Linux内核中的L2层,最终被交付到__netif_receive_skb_core函数中,看下该函数中引用rx_hander的片段
rx_handler = rcu_dereference(skb->dev->rx_handler);
if (rx_handler) {
if (pt_prev) {
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = NULL;
}
switch (rx_handler(&skb)) {
case RX_HANDLER_CONSUMED:
ret = NET_RX_SUCCESS;
goto unlock;
case RX_HANDLER_ANOTHER:
goto another_round;
case RX_HANDLER_EXACT:
deliver_exact = true;
}
case RX_HANDLER_PASS:
break;
default:
BUG();
}
可以看到这里首先从设备结构net_device中获取其rx_handler指针,该指针在网卡的混杂模式下指向一个处理函数叫做br_handle_frame,即网桥的处理流程
rx_handler_result_t br_handle_frame(struct sk_buff **pskb)
{
struct net_bridge_port *p;
struct sk_buff *skb = *pskb;
const unsigned char *dest = eth_hdr(skb)->h_dest;//获取skb的目的MAC
br_should_route_hook_t *rhook; if (unlikely(skb->pkt_type == PACKET_LOOPBACK))
return RX_HANDLER_PASS; if (!is_valid_ether_addr(eth_hdr(skb)->h_source))
goto drop; skb = skb_share_check(skb, GFP_ATOMIC);
/*只有skb是共享的且clone的时候分配内存出错skb才会是null*/
if (!skb)
return RX_HANDLER_CONSUMED; p = br_port_get_rcu(skb->dev); if (unlikely(is_link_local_ether_addr(dest))) {
/*
* See IEEE 802.1D Table 7-10 Reserved addresses
*
* Assignment Value
* Bridge Group Address 01-80-C2-00-00-00
* (MAC Control) 802.3 01-80-C2-00-00-01
* (Link Aggregation) 802.3 01-80-C2-00-00-02
* 802.1X PAE address 01-80-C2-00-00-03
*
* 802.1AB LLDP 01-80-C2-00-00-0E
*
* Others reserved for future standardization
*/
/*目的MAC 地址 ,判断是否是特殊的目的MAC地址*/
switch (dest[]) {
case 0x00: /* Bridge Group Address */
/* If STP is turned off,
then must forward to keep loop detection */
if (p->br->stp_enabled == BR_NO_STP)
goto forward;
break; case 0x01: /* IEEE MAC (Pause) */
goto drop; default:
/* Allow selective forwarding for most other protocols */
if (p->br->group_fwd_mask & (1u << dest[]))
goto forward;
} /* Deliver packet to local host only */
if (NF_HOOK(NFPROTO_BRIDGE, NF_BR_LOCAL_IN, skb, skb->dev,
NULL, br_handle_local_finish)) {
return RX_HANDLER_CONSUMED; /* consumed by filter */
} else {
*pskb = skb;
return RX_HANDLER_PASS; /* continue processing */
}
}
//开始转发
forward:
switch (p->state) {
case BR_STATE_FORWARDING:
rhook = rcu_dereference(br_should_route_hook);
if (rhook) {
if ((*rhook)(skb)) {
*pskb = skb;
return RX_HANDLER_PASS;
}
dest = eth_hdr(skb)->h_dest;
}
/* fall through */
case BR_STATE_LEARNING:
if (ether_addr_equal(p->br->dev->dev_addr, dest))//如果数据包进入的端口的MAC和数据包的目的MAC相同
skb->pkt_type = PACKET_HOST;//表明这是host的数据,需要直接上缴给协议栈 NF_HOOK(NFPROTO_BRIDGE, NF_BR_PRE_ROUTING, skb, skb->dev, NULL,
br_handle_frame_finish);
break;
default:
drop:
kfree_skb(skb);
}
return RX_HANDLER_CONSUMED;
}
这里函数的含义还比较明确,我们先看下所有数据包的类型定义
#define PACKET_HOST /* To us */
#define PACKET_BROADCAST 1 /* To all */
#define PACKET_MULTICAST 2 /* To group */
#define PACKET_OTHERHOST 3 /* To someone else */
#define PACKET_OUTGOING 4 /* Outgoing of any type */
/* These ones are invisible by user level */
#define PACKET_LOOPBACK 5 /* MC/BRD frame looped back */
#define PACKET_FASTROUTE 6 /* Fastrouted frame */
数据包的这个特性记录在skb->pkt_type字段中,只是占用三个bit位。
继续看函数体
在函数中,前半部分都是一些验证,这里首先验证数据包的类型,然后验证数据包中源mac地址的合法性,
接着检查skb是否是共享的,这一些都通过后会判断目的MAC地址是否是特殊的MAC地址,虽然这一可能性不大,但是还是要判断下。这里判断的内容不是本文重点,就不在详细描述。
然后就到了forward节:
这里根据端口的state做switch
在BR_STATE_FORWARDING状态下,调用了一个hook函数。这部分内容还不是很理解。
而在BR_STATE_LEARNING状态下,首先判断了目的MAC是否和数据流入端口的mac地址是否相同,相同就表明数据包是发往本机的,设置skb的包类型为PACKET_HOST,然后调用了
br_handle_frame_finish函数
int br_handle_frame_finish(struct sk_buff *skb)
{
const unsigned char *dest = eth_hdr(skb)->h_dest;
struct net_bridge_port *p = br_port_get_rcu(skb->dev);
struct net_bridge *br;
struct net_bridge_fdb_entry *dst;
struct net_bridge_mdb_entry *mdst;
struct sk_buff *skb2;
bool unicast = true;
u16 vid = ;
//如果端口不可用,则直接丢弃数据包
if (!p || p->state == BR_STATE_DISABLED)
goto drop;
//对vlan标签做相关判断,查看skb是否符合
if (!br_allowed_ingress(p->br, nbp_get_vlan_info(p), skb, &vid))
goto drop; /* insert into forwarding database after filtering to avoid spoofing */
br = p->br;//获取网桥结构
if (p->flags & BR_LEARNING)//如果网桥具备学习能力,更新转发表
br_fdb_update(br, p, eth_hdr(skb)->h_source, vid);
//如果不是广播地址&&是多播地址&&多播发送成功
if (!is_broadcast_ether_addr(dest) && is_multicast_ether_addr(dest) &&
br_multicast_rcv(br, p, skb))
goto drop;
//此时已经更新表完毕,端口若还是处于学习状态就drop
if (p->state == BR_STATE_LEARNING)
goto drop; BR_INPUT_SKB_CB(skb)->brdev = br->dev; /* The packet skb2 goes to the local host (NULL to skip). */
skb2 = NULL;
//判断网卡若是处于混杂模式
if (br->dev->flags & IFF_PROMISC)
skb2 = skb; dst = NULL;
//如果是广播地址
if (is_broadcast_ether_addr(dest)) {
skb2 = skb;
unicast = false;//设置单播标识为false
} else if (is_multicast_ether_addr(dest)) {
mdst = br_mdb_get(br, skb, vid);
if ((mdst || BR_INPUT_SKB_CB_MROUTERS_ONLY(skb)) &&
br_multicast_querier_exists(br, eth_hdr(skb))) {
if ((mdst && mdst->mglist) ||
br_multicast_is_router(br))
skb2 = skb;
br_multicast_forward(mdst, skb, skb2);
skb = NULL;
if (!skb2)
goto out;
} else
skb2 = skb;
unicast = false;
br->dev->stats.multicast++;
//查找转发表并判断表项,如果表项存在且端口是本地端口
} else if ((dst = __br_fdb_get(br, dest, vid)) &&
dst->is_local) {
skb2 = skb;
/* Do not forward the packet since it's local. */
skb = NULL;
}
//fdb表中存在表项且是本地端口或者多播处理完成 skb2=skb skb=null unicast=true
//广播或者多播未处理完成 skb2=skb skb!=null unicast=false
//fdb表中未找到表项或者不是本地端口 skb!=null skb2=null unicast=true
if (skb) {
if (dst) {
//转发表中表项存在且不是本地端口,即需要转发到其他端口
dst->used = jiffies;
//实施转发
br_forward(dst->dst, skb, skb2);
} else
//处理广播或者多播或者未找到端口的单播
br_flood_forward(br, skb, skb2, unicast);
}
//目的端口是本地端口&&多播&&广播
if (skb2)
return br_pass_frame_up(skb2); out:
return ;
drop:
kfree_skb(skb);
goto out;
}
这里就要做比较详细的判断了,首先判断端口的状态,然后调用br_allowed_ingress函数验证vlan标签,这里就不深入去查看了。接着就调用br_fdb_update更新网桥的转发表,对组播数据包进行预处理。
接着就开始了数据包转发前的地址判断,先判断是否是广播地址,是就令skb2=skb即复制一份数据包,并设置unicast为false。
然后判断是否是组播地址,是就从组播数据库中获取对应的net_bridge_mdb_entry结构,该结构中记录了组播组中的端口,在经过几个验证之后就调用br_multicast_forward进行组播数据包的转发,之后置空skb
最后就剩下单播地址了,从地址转发表中获取net_bridge_fdb_entry结构并判断其is_local属性,如果is_lcoal为true则表示这个发往host的数据包,就设置复制一份skb,然后置空skb指针。
然后就开始其他端口的转发,这里在前一部分已经根据不同的情况设置了skb指针,所以如果skb指针不为空就表示这是单播或者广播或者多播未处理的情况,然后判断前面获取的单播转发表的表项是否为空,如果不为空就表示这个发往其他端口的单播数据包,那么就调用br_forward进行转发。如果为空就表示这有可能是其他的情况,那么就调用br_flood_forward进行处理,注意这里还有一个参数就是unicast,这是单播标识,函数中会用到。
br_flood_forward会把单播数据包发往所有支持BR_FLOOD特性的端口,上面也许注意到了不只是单播数据包可以走到这里,广播也可以走到这里,这也难怪,单播在未找到表项的情况下只能向所有其他的支持BR_FLOOD特性的端口转发,这和广播很相似,只不过是广播的话BR_FLOOD特性也不起作用,直接全部转发了。只是我不太明白的是未处理的组播数据包怎么办了??
接着就处理本地数据包的情况,即数据包目的地址是host的单播数据、广播、组播都需要给host上层交付,那么这里就调用br_pass_frame_up函数
static int br_pass_frame_up(struct sk_buff *skb)
{
struct net_device *indev, *brdev = BR_INPUT_SKB_CB(skb)->brdev;
struct net_bridge *br = netdev_priv(brdev);
struct br_cpu_netstats *brstats = this_cpu_ptr(br->stats); u64_stats_update_begin(&brstats->syncp);
brstats->rx_packets++;
brstats->rx_bytes += skb->len;
u64_stats_update_end(&brstats->syncp); /* Bridge is just like any other port. Make sure the
* packet is allowed except in promisc modue when someone
* may be running packet capture.
*/
if (!(brdev->flags & IFF_PROMISC) &&
!br_allowed_egress(br, br_get_vlan_info(br), skb)) {
kfree_skb(skb);
return NET_RX_DROP;
} skb = br_handle_vlan(br, br_get_vlan_info(br), skb);
if (!skb)
return NET_RX_DROP; indev = skb->dev;
skb->dev = brdev; return NF_HOOK(NFPROTO_BRIDGE, NF_BR_LOCAL_IN, skb, indev, NULL,
netif_receive_skb);
}
到了该函数已经要准备把数据交付给网络层了,并且已经设置数据包的设备skb->dev修改为网桥代表的设备,表明这是从网桥发出的数据包。最后会再次调用netif_receive_skb重新接受数据包但是这时skb->dev是网桥,并且网桥设备的rx_handler指针肯定为空,那么就不会再次进入网桥的处理,而是直接交付上层了。
br_flood_forward
LInux下桥接模式详解三的更多相关文章
- Linux下桥接模式详解一
注册博客园已经好长时间,一直以来也没有在上面写过文章,都是随意的记录在了未知笔记上,今天开始本着分享和学习的精神想把之前总结的笔记逐步分享到博客园,和大家一起学习,一起进步吧! 2016-09-20 ...
- LInux下桥接模式详解二
上篇文章导入博客园的比较早,而这篇自己在写的时候才发现内部复杂的很,以至于没能按时完成,造成两篇文章的间隔时间有点长! 话不多说,言归正传! 前面的文章介绍了桥接模式下的基础理论知识,其实本节想结合L ...
- Linux网络配置:Nat和桥接模式详解
Linux网络配置:Nat和桥接模式详解 一.我们首先说一下VMware的几个虚拟设备: Centos虚拟网络编辑器中的虚拟交换机: VMnet0:用于虚拟桥接网络下的虚拟交换机: VMnet1:用于 ...
- linux下tar命令详解
linux下tar命令详解 tar是Linux环境下最常用的备份工具之一.tar(tap archive)原意为操作磁带文件,但基于Linux的文件操作机制,同样也可适用于普通的磁盘文件.ta ...
- Linux下top命令详解
Linux下top命令详解 top命令是Linux下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于Windows的任务管理器.top是一个动态显示过程,即可以通过用户按键来不断刷 ...
- Linux下chkconfig命令详解(转)
Linux下chkconfig命令详解 chkconfig命令主要用来更新(启动或停止)和查询系统服务的运行级信息.谨记chkconfig不是立即自动禁止或激活一个服务,它只是简单的改变了符号连接. ...
- Linux知识积累(4) Linux下chkconfig命令详解
Linux下chkconfig命令详解 chkconfig命令主要用来更新(启动或停止)和查询系统服务的运行级信息.谨记chkconfig不是立即自动禁止或激活一个服务,它只是简单的改变了符号连接. ...
- 转载的 Linux下chkconfig命令详解
Linux下chkconfig命令详解 chkconfig命令主要用来更新(启动或停止)和查询系统服务的运行级信息.谨记chkconfig不是立即自动禁止或激活一个服务,它只是简单的改变了符号连接. ...
- linux下IPTABLES配置详解 (防火墙命令)
linux下IPTABLES配置详解 -A RH-Firewall-1-INPUT -p tcp -m state --state NEW -m tcp --dport 24000 -j ACCEPT ...
随机推荐
- [C++]怎么将.h和.cpp文件分别放在不同的目录
相关资料: http://blog.csdn.net/onafioo/article/details/8775501 具体操作: 1.找到.h文件目录.2.将所以的.h文件剪切到“include”目录 ...
- Lucene 工作原理<转>
Lucene是一个高性能的java全文检索工具包,它使用的是倒排文件索引结构.该结构及相应的生成算法如下: 0)设有两篇文章1和2 文章1的内容为:Tom lives in Guangzhou,I l ...
- 嵌入式linux性能详解_转
最近简单看了下<嵌入式Linux性能详解>一书,对系统内存分布测试.程序运行.动态库等都很很好的解析. 作者史子旺,loughsky@sina.com. 有时间希望仔细通读,并验证.
- js 刷新后不提示并保留控件状态
保存后,想提示一下并保留查询条件的状态,发现可以用document.forms[0].submit();继续提交达到刷新的目的 代码如下: ScriptManager.RegisterStartupS ...
- 02 观察 mysql 周期性变化
()首先写一个shell 脚本 vim mysql_status.sh 脚本如下: #!bin/bash while true do mysqladmin -urooy ext|awk '/Queri ...
- MyBatis学习4---使用MyBatis_Generator生成Dto、Dao、Mapping
由于MyBatis属于一种半自动的ORM框架,所以主要的工作将是书写Mapping映射文件,但是由于手写映射文件很容易出错,所以查资料发现有现成的工具可以自动生成底层模型类.Dao接口类甚至Mappi ...
- java-I/O File类(5)-Reader和Writer、OutputStreamWriter 、BufferedWriter、字节流和字符流的区别
标签: outputstreamwriterreader字符file方法 2015-05-14 23:06 469人阅读 评论(0) 收藏 举报 分类: 孙鑫-java基础(16) I-O(4 ...
- C#中DllImport用法汇总
最近使用DllImport,从网上google后发现,大部分内容都是相同,又从MSDN中搜集下,现将内容汇总,与大家分享. 大家在实际工作学习C#的时候,可能会问:为什么我们要为一些已经存在的功能(比 ...
- 编程之美 set 21 24点游戏
题目 输入: n1, n2, n3, n4 (1~13) 输出: 若能得到运算结果为 24, 则输出一个对应的运算表达式 如: 输入: 11, 8, 3, 5 输出: (11-8) * (3*5) = ...
- Linux中使用SecureCRT上传、下载文件命令sz与rz用法实例
来自:http://www.jb51.net/LINUXjishu/163820.html 其中,对于sz和rz的理解与记忆我用了如下的方法(因为很多时候容易搞混):sz中的s意为send(发送),告 ...