linux协议栈之网桥实现之一

190 篇文章 1 订阅


网卡驱动的最后一个函数是netif_receive_skb.就从它说起。
为了简单起见,去掉了里面预编译代码
int netif_receive_skb(struct sk_buff *skb)         (net/core/dev.c)
{
         struct packet_type *ptype, *pt_prev;
         int ret = NET_RX_DROP;
         unsigned short type;
         //打上接收的时间戳
         if (!skb->stamp.tv_sec)
                   net_timestamp(&skb->stamp);
         //如果存在dev->master。则更新相应指针
         skb_bond(skb);
         //更新CPU的接收统计数据
         __get_cpu_var(netdev_rx_stat).total++;
         skb->h.raw = skb->nh.raw = skb->data;
         skb->mac_len = skb->nh.raw - skb->mac.raw;
         pt_prev = NULL;
         rcu_read_lock();
         //处理所有协议的模块
         list_for_each_entry_rcu(ptype, &ptype_all, list) {
                   if (!ptype->dev || ptype->dev == skb->dev) {
                            if (pt_prev) 
                                     ret = deliver_skb(skb, pt_prev);
                            pt_prev = ptype;
                   }
         }
         //分片处理
         handle_diverter(skb);
         //网桥处理
         if (handle_bridge(&skb, &pt_prev, &ret))
                   goto out;
         type = skb->protocol;
         //为协议调用相应模块处理。
         list_for_each_entry_rcu(ptype, &ptype_base[ntohs(type)&15], list) {
                   if (ptype->type == type &&
                       (!ptype->dev || ptype->dev == skb->dev)) {
                            if (pt_prev) 
                                     ret = deliver_skb(skb, pt_prev);
                            pt_prev = ptype;
                   }
         }
         if (pt_prev) {
                   ret = pt_prev->func(skb, skb->dev, pt_prev);
         } else {
                   kfree_skb(skb);
                   /* Jamal, now you will not able to escape explaining
                    * me how you were going to use this. :-)
                    */
                   ret = NET_RX_DROP;
         }

out:
         rcu_read_unlock();
         return ret;
}
此函数主要完成了分片重组,网桥处理,根据不同协议调用不同的传输层处理模块。本节的重点是概述linux的网桥实现与处理。传输层协议分层将在后续章节陆续给出。进入网桥处理代码:
#if defined(CONFIG_BRIDGE) || defined (CONFIG_BRIDGE_MODULE)                 (net/core/dev.c)
int (*br_handle_frame_hook)(struct net_bridge_port *p, struct sk_buff **pskb);

static __inline__ int handle_bridge(struct sk_buff **pskb,
                                         struct packet_type **pt_prev, int *ret)
{
         struct net_bridge_port *port;
         //回环接口?非以太网接口?
         if ((*pskb)->pkt_type == PACKET_LOOPBACK ||
             (port = rcu_dereference((*pskb)->dev->br_port)) == NULL)
                   return 0;

         if (*pt_prev) {
                   *ret = deliver_skb(*pskb, *pt_prev);
                   *pt_prev = NULL;
         } 
         // br_handle_frame_hook是一个全局的函数指针
         return br_handle_frame_hook(port, pskb);
}
#else
#define handle_bridge(skb, pt_prev, ret) (0)
#endif
从此可以看出。如果编译的时候选择了网桥模式,则会进入网桥的处理模块了,否则,只是一个空函数,直接返回。br_handle_frame_hook代表的函数是什么呢?网桥的数据处理框架又是什么样的呢?


关于网桥:
网桥是一个二层设备,在深入之前可以把它当成一个二层的交换机。它在二层协议上转发数据。
网桥为了转发数据,维持了一个端口与MAC的对应表,通常通为CAM表。根据这张表可以把数据送往相应的端口进行发送.
网桥的转发过程为:
1:接收到一个包。判断自己的CAM表中是否含包它此包的源地址.如果没有,则把源地址与端口更新至于CAM表.
2:判断包是否是送给本机,如果是,则送往本机上层协议栈处理。如果不是,则查寻CAM表。找到相应的出口。
3:如果找到出口,则将此包送至出口。如果不存在,将会在各端口发送。
4:如果CAM表中对应表项在规定时间之内没有得到更新,则删除此项。
网桥的配置:
Brctl是一个比较好的配置网桥的工具。它的源代码和配置方法极其简单。我们将从网桥的配置流程说起,看linux内核是怎样一步步管理的。
首先,创建一个网桥: brctl addbr br0             (建立一个br0的网桥)
然后,将接口添加进网桥:brctl addif  br0 eth0   (将eth0和eth1添加进网桥br0)
                                               brctl addif   bro eth1
OK,网桥现在就配置好了。这台linux的主机可以当作交换机使用了,从eth0的包都可以转发到eth1。
现在,我们看下代码中如何进行处理
首先 brctl addbr 。查看brctl的代码发现它调用了:ioctl(br_socket_fd, SIOCBRADDBR, brname);
然后 brctl addif    在brctl的代码中调用了:ioctl(br_socket_fd, SIOCBRADDIF, &ifr);
呵呵。Brctl的代码很简单吧,只是调用了用户空间的配置工具ioctl.
Linux网桥分析:
好了,现在就可以进入内核分析网桥模式了:
static int __init br_init(void)                                                (net/brige/br.c)
{
         //分配slab缓冲区
         br_fdb_init();
//网桥的netfiter处理,将在以后的章节中分析
#ifdef CONFIG_BRIDGE_NETFILTER
         if (br_netfilter_init())
                   return 1;
#endif
         //用户空间ioctl调用的函数
         brioctl_set(br_ioctl_deviceless_stub);
         //接收到数据包的处理,也就是我们在上面netif_receive_skb函数中看到的br_handle_frame_hook
         br_handle_frame_hook = br_handle_frame;

#if defined(CONFIG_ATM_LANE) || defined(CONFIG_ATM_LANE_MODULE)
         br_fdb_get_hook = br_fdb_get;
         br_fdb_put_hook = br_fdb_put;
#endif
         //在netdev_chain通知链表上注册。关于通知链表,在前面已经介绍过,这里不再讨论了
         register_netdevice_notifier(&br_device_notifier);

         return 0;
}


新建网桥:
从上面的分析可以知道,在用户空间调用ioctl(br_socket_fd, SIOCBRADDBR, brname).进入到br_ioctl_deviceless_stub,可以看到它的相关处理:
int br_ioctl_deviceless_stub(unsigned int cmd, void __user *uarg)
{
         switch (cmd) {
         case SIOCGIFBR:
         case SIOCSIFBR:
                   return old_deviceless(uarg);
         //新建网桥
         case SIOCBRADDBR:
         //删除网桥
         case SIOCBRDELBR:
         {
                   char buf[IFNAMSIZ];
                   if (!capable(CAP_NET_ADMIN))
                            return -EPERM;
                   //copy_from_user:把用户空间的数据拷入内核空间
                   if (copy_from_user(buf, uarg, IFNAMSIZ))
                            return -EFAULT;
                   buf[IFNAMSIZ-1] = 0;
                   if (cmd == SIOCBRADDBR)
                            return br_add_bridge(buf);
                   return br_del_bridge(buf);
         }
         }
         return -EOPNOTSUPP;
}
在这里,我们传入的cmd为SIOCBRADDBR.转入br_add_bridge(buf)中进行:
int br_add_bridge(const char *name)
{
         struct net_device *dev;
         int ret;
         //为虚拟桥新建一个net_device
         //在前面“网络设备的管理”经讲述此结构
         dev = new_bridge_dev(name);
         if (!dev) 
                   return -ENOMEM;

         rtnl_lock();
         //由内核确定接口名字,例如eth0 eth1等
         if (strchr(dev->name, '%')) {
                   ret = dev_alloc_name(dev, dev->name);
                   if (ret 
                            goto err1;
         }
         //向内核注册此网络设备
         ret = register_netdevice(dev);
         if (ret)
                   goto err2;
         dev_hold(dev);
         rtnl_unlock();
         //在sysfs中建立相关信息
         ret = br_sysfs_addbr(dev);
         dev_put(dev);
         if (ret) 
                   unregister_netdev(dev);
out:
         return ret;
err2:
         free_netdev(dev);
err1:
         rtnl_unlock();
         goto out;
}
网桥的注册跟我们以前看到的物理网络设备注册是一样的。我们关心的是网桥对应的net_device结构是什么样的,继续跟踪进new_bridge_dev:
static struct net_device *new_bridge_dev(const char *name)
{
         struct net_bridge *br;
         struct net_device *dev;
         //分配net_device
         dev = alloc_netdev(sizeof(struct net_bridge), name,
                               br_dev_setup);
         if (!dev)
                   return NULL;
         网桥的私区结构为net_bridge
         br = netdev_priv(dev);
         //私区结构中的dev字段指向它本身
         br->dev = dev;
         br->lock = SPIN_LOCK_UNLOCKED;
         //队列初始化。在port_list中保存了这个桥上的端口列表
         INIT_LIST_HEAD(&br->port_list);
         br->hash_lock = SPIN_LOCK_UNLOCKED;
         //下面这部份代码跟stp协议相关,我们暂不关心
         br->bridge_id.prio[0] = 0x80;
         br->bridge_id.prio[1] = 0x00;
         memset(br->bridge_id.addr, 0, ETH_ALEN);

         br->stp_enabled = 0;
         br->designated_root = br->bridge_id;
         br->root_path_cost = 0;
         br->root_port = 0;
         br->bridge_max_age = br->max_age = 20 * HZ;
         br->bridge_hello_time = br->hello_time = 2 * HZ;
         br->bridge_forward_delay = br->forward_delay = 15 * HZ;
         br->topology_change = 0;
         br->topology_change_detected = 0;
         br->ageing_time = 300 * HZ;
         INIT_LIST_HEAD(&br->age_list);

         br_stp_timer_init(br);

         return dev;
}
在br_dev_setup中还做了一些另外在函数指针初始化:
void br_dev_setup(struct net_device *dev)
{
         //将桥的MAC地址设为零
         memset(dev->dev_addr, 0, ETH_ALEN);
         //以太网结构初始化
         ether_setup(dev);
         //一系列函数指针初始化
         dev->do_ioctl = br_dev_ioctl;
         dev->get_stats = br_dev_get_stats;
         dev->hard_start_xmit = br_dev_xmit;
         dev->open = br_dev_open;
         dev->set_multicast_list = br_dev_set_multicast_list;
         dev->change_mtu = br_change_mtu;
         dev->destructor = free_netdev;
         SET_MODULE_OWNER(dev);
         dev->stop = br_dev_stop;
         dev->accept_fastpath = br_dev_accept_fastpath;
         dev->tx_queue_len = 0;
         dev->set_mac_address = NULL;
         dev->priv_flags = IFF_EBRIDGE;
}
这一部份,对桥设备的私区空间进行了初始化。在这里,有必要给桥的net_device对应的私区结构:
struct net_bridge
{
         //读写锁
         spinlock_t                    lock;
         //端口列表
         struct list_head             port_list;
         //网桥对应的虚拟设备
         struct net_device          *dev;
         //网桥对应的虚拟网卡的统计数据
         struct net_device_stats                   statistics;
         //hash表的锁
         spinlock_t                    hash_lock;
         //MAC PORT对应表,即CAM
         struct hlist_head           hash[BR_HASH_SIZE];
         struct list_head             age_list;

         /* STP */
         //与stp 协议对应的数据
         bridge_id                       designated_root;
         bridge_id                       bridge_id;
         u32                               root_path_cost;
         unsigned long                         max_age;
         unsigned long                         hello_time;
         unsigned long                         forward_delay;
         unsigned long                         bridge_max_age;
         unsigned long                         ageing_time;
         unsigned long                         bridge_hello_time;
         unsigned long                         bridge_forward_delay;

         u16                               root_port;
         unsigned char                         stp_enabled;
         unsigned char                         topology_change;
         unsigned char                         topology_change_detected;

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值