网卡驱动的最后一个函数是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; |