Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1795455
  • 博文数量: 306
  • 博客积分: 3133
  • 博客等级: 中校
  • 技术积分: 3932
  • 用 户 组: 普通用户
  • 注册时间: 2009-04-19 16:50
文章分类

全部博文(306)

文章存档

2018年(7)

2017年(18)

2016年(39)

2015年(35)

2014年(52)

2013年(39)

2012年(22)

2011年(29)

2010年(53)

2009年(12)

分类: LINUX

2014-03-25 09:38:07

Linux网桥模型:

  Linux内核通过一个虚拟的网桥设备来实现桥接的,这个设备可以绑定若干个以太网接口设备,从而将它们桥接起来。如下图所示:

  网桥设备br0绑定了eth0和eth1。对于网络协议栈的上层来说,只看得到br0,因为桥接是在数据链路层实现的,上层不需要关心桥接的细节。于是协议栈上层需要发送的报文被送到br0,网桥设备的处理代码再来判断报文该被转发到eth0或是eth1,或者两者皆是;反过来,从eth0或从eth1接收到的报文被提交给网桥的处理代码,在这里会判断报文该转发、丢弃、或提交到协议栈上层。

  而有时候eth0、eth1也可能会作为报文的源地址或目的地址,直接参与报文的发送与接收(从而绕过网桥)。

  相关数据结构:

  其中最左边的net_device是一个代表网桥的虚拟设备结构,它关联了一个net_bridge结构,这是网桥设备所特有的数据结构。

  在net_bridge结构中,port_list成员下挂一个链表,链表中的每一个节点(net_bridge_port结构)关联到一个真实的网口设备的net_device。网口设备也通过其br_port指针做反向的关联(那么显然,一个网口最多只能同时被绑定到一个网桥)。

  net_bridge结构中还维护了一个hash表,是用来处理地址学习的。当网桥准备转发一个报文时,以报文的目的Mac地址为key,如果可以在hash表中索引到一个net_bridge_fdb_entry结构,通过这个结构能找到一个网口设备的net_device,于是报文就应该从这个网口转发出去;否则,报文将从所有网口转发。

  网桥数据包的处理流程:

  接收过程:

  对于数据包的处理流程并没有明显的主线,主要就是根据内核代码中网桥部分的源码进行分析。

  网口设备接收到的报文最终通过net_receive_skb函数被网络协议栈所接收。这个函数主要做三件事情:1、如果有抓包程序需要skb,将skb复制给它们;2、处理桥接;3、将skb提交给网络层。

  int netif_receive_skb(struct sk_buff *skb)

  {

  ......

  if (handle_bridge(&skb, &pt_prev, &ret, orig_dev))

  goto out;

  ......

  }

  static inline struct sk_buff *handle_bridge(struct sk_buff *skb,

  struct packet_type **pt_prev, int *ret,

  struct net_device *orig_dev)

  {

  struct net_bridge_port *port;

  //对于回环设备以及skb->dev->br_port为空(即不被任何网桥所包含)的数据包直接返回

  if (skb->pkt_type == PACKET_LOOPBACK ||

  (port = rcu_dereference(skb->dev->br_port)) == NULL)

  return skb;

  if (*pt_prev) {

  *ret = deliver_skb(skb, *pt_prev, orig_dev);

  *pt_prev = NULL;

  }

  //网桥的基本挂接点处理函数

  return br_handle_frame_hook(port, skb);

  }

  br_handle_frame_hook在网桥初始化模块br_init(void)函数中被赋值.

  br_handle_frame_hook = br_handle_frame;

  所以网桥对于数据包的处理过程是从br_handle_frame开始的。

  struct sk_buff *br_handle_frame(struct net_bridge_port *p, struct sk_buff *skb)

  {

  const unsigned char *dest = eth_hdr(skb)->h_dest;

  int (*rhook)(struct sk_buff *skb);

  //判断是否为有效的物理地址,非全0地址以及非广播地址

  if (!is_valid_ether_addr(eth_hdr(skb)->h_source))

  goto drop;

  //判断skb包是否被共享skb->users != 1,若是,则复制一份,否则直接返回

  skb = skb_share_check(skb, GFP_ATOMIC);

  if (!skb)

  return NULL;

  //这个函数并非像想象的那样,判断是否为本地地址

  //而是在判断是否为链路本地多播地址,01:80:c2:00:00:0x

  if (unlikely(is_link_local(dest))) {

  /* Pause frames shouldn't be passed up by driver anyway */

  if (skb->protocol == htons(ETH_P_PAUSE))

  goto drop;

  /* If STP is turned off, then forward */

  if (p->br->stp_enabled == BR_NO_STP && dest[5] == 0)


goto forward;

  if (NF_HOOK(PF_BRIDGE, NF_BR_LOCAL_IN, skb, skb->dev,

  NULL, br_handle_local_finish))

  return NULL;  /* frame consumed by filter */

  else

  return skb;      /* continue processing */

  }

  forward:

  switch (p->state) {

  case BR_STATE_FORWARDING:

  //如果网桥处于forwarding状态,并且该报文必须要走L3层进行转发,则直接返回

  //br_should_route_hook钩子函数在ebtable里面设置为ebt_broute函数,它根据用户的规

  //决定该报文是否要通过L3层来转发;一般rhook为空

  rhook = rcu_dereference(br_should_route_hook);

  if (rhook != NULL) {

  if (rhook(skb))

  return skb;

  dest = eth_hdr(skb)->h_dest;

  }

  /* fall through */

  case BR_STATE_LEARNING:

  //如果数据包的目的mac地址为虚拟网桥设备的mac地址,则标记为host

  if (!compare_ether_addr(p->br->dev->dev_addr, dest))

  skb->pkt_type = PACKET_HOST;

  //调用网桥在NF_BR_PREROUTING处挂载的钩子函数,因为网桥在其钩子函数过//程中嵌套调用了INET层BR_PREROUTING的钩子函数,过程有些曲折,故最后//再分析

  NF_HOOK(PF_BRIDGE, NF_BR_PRE_ROUTING, skb, skb->dev, NULL,

  br_handle_frame_finish);

  break;

  default:

  drop:

  kfree_skb(skb);

  }

  return NULL;

  }

  FORWARDING以及LEARNING为网桥的状态,网桥端口一般有5种状态:

  1)  disable 被管理员禁用

  2)  blcok 休息,不参与数据包转发

  3)  listening 监听

  4)  learning 学习ARP信息,准备向工作状态改变

  5)  forwarding 正常工作,转发数据包

  /* note: already called with rcu_read_lock (preempt_disabled) */

  int br_handle_frame_finish(struct sk_buff *skb)

  {

  const unsigned char *dest = eth_hdr(skb)->h_dest;

  struct net_bridge_port *p = rcu_dereference(skb->dev->br_port);

  struct net_bridge *br;

  struct net_bridge_fdb_entry *dst;

  struct sk_buff *skb2;

  //判断网桥状态

  if (!p || p->state == BR_STATE_DISABLED)

  goto drop;

  /* insert into forwarding database after filtering to avoid spoofing */

  //br为虚拟网桥结构

  br = p->br;

  //根据数据包的源物理地址,更新网桥的转发表

  br_fdb_update(br, p, eth_hdr(skb)->h_source);

  if (p->state == BR_STATE_LEARNING)

  goto drop;

  /* The packet skb2 goes to the local host (NULL to skip). */

  //skb2数据包用于交付本机,skb数据包则用于forward

  skb2 = NULL;

  //如果网口处于混杂模式,复制一份交付主机

  if (br->dev->flags & IFF_PROMISC)

  skb2 = skb;

  dst = NULL;

  //如果为广播数据包,增加计数,同样需要发一份给主机

  if (is_multicast_ether_addr(dest)) {

  br->dev->stats.multicast++;

  skb2 = skb;

  }

  /*根据网桥口以及目标地址判断是否为本机数据包*/

  else if ((dst = __br_fdb_get(br, dest)) && dst->is_local) {

  skb2 = skb;

  /* Do not forward the packet since it's local. */

  skb = NULL;

  }

  if (skb2 == skb)

  skb2 = skb_clone(skb, GFP_ATOMIC);

  if (skb2)

  /*完成将数据包交付给本机的工作*/

  br_pass_frame_up(br, skb2);

  if (skb) {

  if (dst)

  //如果存在目的地址则将其转发

  br_forward(dst->dst, skb);

  else

  //否则,flood数据包,向除接收网口外的其余网口发送该数据包

  br_flood_forward(br, skb);

  }

  out:

  return 0;

  drop:

  kfree_skb(skb);

  goto out;

  }

  //此函数主要实现通过网桥接收发往本机的数据包

  static void br_pass_frame_up(struct net_bridge *br, struct sk_buff *skb)

  {

  struct net_device *indev, *brdev = br->dev;

  //完成数据包的统计计数

  brdev->stats.rx_packets++;

  brdev->stats.rx_bytes += skb->len;

  //将skb的dev改变为网桥结构的brdev

阅读(3294) | 评论(1) | 转发(0) |
给主人留下些什么吧!~~

njzhiyuan2014-12-24 11:15:15

写得好,学习