本文代码基于linux2.6.21
Vlan即虚拟局域网,一个vlan能够模拟一个常规的交换网络,实现了将一个物理的交换机划分成多个逻辑的交换网络。而不同的vlan之间如果要进行通信就要通过三层协议来实现。
在linux中vlan的配置使用vconfig,使用vconfig配置一个交换网络,大致的流程如下:
#
# vconfig add eth0 100
#
# ifconfig eth0.100 up
# brctl addbr br1
# ifconfig br1 up
# brctl addif br1 eth0.100
# brctl addif br1 eth2
这样就实现了将从eth0 口进来的vlan id 为100的数据流与eth2放在了一个逻辑交换网络中。
下面开始分析linux vlan模块
一、相关的数据结构
1.1 struct vlan_ethhdr
包含vlan头部的二层头部结构体
struct vlan_ethhdr {
unsigned char h_dest[ETH_ALEN]; /* destination eth addr */
unsigned char h_source[ETH_ALEN]; /* source ether addr */
__be16 h_vlan_proto; /* Should always be 0x8100 */
__be16 h_vlan_TCI; /* Encapsulates priority and VLAN ID */
__be16 h_vlan_encapsulated_proto; /* packet type ID field (or len) */
};
1.2 struct vlan_hdr
vlan头部关联的结构体
struct vlan_hdr {
__be16 h_vlan_TCI; /* Encapsulates priority and VLAN ID */
__be16 h_vlan_encapsulated_proto; /* packet type ID field (or len) */
};
1.3 struct vlan_group
该结构体主要是实现将一个real device关联的vlan device设备存放在vlan_devices_arrays,
(该变量首先是一个数组,数组里的每一个指针都是一个二级指针)
struct vlan_group {
/*real device的index*/
int real_dev_ifindex; /* The ifindex of the ethernet(like) device the vlan is attached to. */
struct hlist_node hlist; /* linked list */
struct net_device **vlan_devices_arrays[VLAN_GROUP_ARRAY_SPLIT_PARTS];
struct rcu_head rcu;
};
该变量也是比较重要的一个数据结构,可以保证一个real device针对每一个vlan id创建的vlan device都是唯一的。
Vlan group是通过全局变量vlan_group_hash的hash链表链接在一起的,其定义如下:
static struct hlist_head vlan_group_hash[VLAN_GRP_HASH_SIZE];
由于vlan device、real device都是使用的struct net_device变量,因此目前代码中,没有将vlan_group定义在net_device中,而是重新定义了一个全局的hash链表数组中。
那么问题来了,可不可以在struct net_device中增加一个链表头指针,将该real device
关联的vlan device设备都添加到该链表中呢?
当然是可以的,这样做的话,在查找一个real device关联的所有vlan device时,其查找
时间肯定比当前的查找时间要快的(因为不用进行hash相关的操作),但是每一个struct net_device变量都增加了一个链表指针,增加了对内存的使用,这个就需要在内存与性能之间做取舍了。
相应的set与get的接口函数如下:
/*
功能:根据vlan id以及vlan_group变量,查找符合条件的vlan device
*/
static inline struct net_device *vlan_group_get_device(struct vlan_group *vg, int vlan_id)
{
struct net_device **array;
array = vg->vlan_devices_arrays[vlan_id / VLAN_GROUP_ARRAY_PART_LEN];
return array[vlan_id % VLAN_GROUP_ARRAY_PART_LEN];
}
/*
功能:根据vlan id 以及vlan group变量,在vlan_devices_arrays中增加相应的vlan设备
由于是直接增加设备,没有进行存在性判断,因此在调用该函数
之前,应该调用vlan_group_get_device判断要添加的vlan设备是否已存在。
*/
static inline void vlan_group_set_device(struct vlan_group *vg, int vlan_id,
struct net_device *dev)
{
struct net_device **array;
if (!vg)
return;
array = vg->vlan_devices_arrays[vlan_id / VLAN_GROUP_ARRAY_PART_LEN];
array[vlan_id % VLAN_GROUP_ARRAY_PART_LEN] = dev;
}
1.4 struct vlan_dev_info
vlan设备相关的信息结构体
该结构体定义了vlan device相关的一些信息,包括输入/输出方向优先级映射、
vlan id、关联的real device、性能统计相关的信息等。
该结构相关的变量被定义在struct net_device的priv指针变量指向的内存空间中。
#define VLAN_DEV_INFO(x) ((struct vlan_dev_info *)(x->priv))
struct vlan_dev_info {
/** This will be the mapping that correlates skb->priority to
* 3 bits of VLAN QOS tags...
*/
/*
输入数据包的优先级与vlan优先级的map
*/
unsigned long ingress_priority_map[8];
struct vlan_priority_tci_mapping *egress_priority_map[16]; /* hash table */
/*vlan id*/
unsigned short vlan_id; /* The VLAN Identifier for this interface. */
/*
vlan device 是否支持reorder header,即rx方向,实现剥除掉数据包的vlan操作
*/
unsigned short flags; /* (1 << 0) re_order_header This option will cause the
* VLAN code to move around the ethernet header on
* ingress to make the skb look **exactly** like it
* came in from an ethernet port. This destroys some of
* the VLAN information in the skb, but it fixes programs
* like DHCP that use packet-filtering and don't understand
* 802.1Q
*/
/*
以下三个参数是与组播相关的
*/
struct dev_mc_list *old_mc_list; /* old multi-cast list for the VLAN interface..
* we save this so we can tell what changes were
* made, in order to feed the right changes down
* to the real hardware...
*/
int old_allmulti; /* similar to above. */
int old_promiscuity; /* similar to above. */
/*
vlan设备所依附的真实设备
*/
struct net_device *real_dev; /* the underlying device/interface */
/*
proc文件系统相关的参数
*/
struct proc_dir_entry *dent; /* Holds the proc data */
unsigned long cnt_inc_headroom_on_tx; /* How many times did we have to grow the skb on TX. */
unsigned long cnt_encap_on_xmit; /* How many times did we have to encapsulate the skb on TX. */
/*
vlan设备相关联的
*/
struct net_device_stats dev_stats; /* Device stats (rx-bytes, tx-pkts, etc...) */
};
以上就是主要相关的数据结构,下面分析下相应的功能。
二、linux vlan 架构分析
对于linux vlan架构来说,主要分为四个方面。
1. Struct net_device中相应接口函数的实现,既然vlan device使用的也是struct net_device变量,因此针对vlan device就需要实现相应的接口函数,包括vlan_dev_hard_header、vlan_dev_hard_start_xmit、vlan_dev_open、vlan_dev_stop
2. Linux vlan模块与socket模块的关联,因为linux vlan 模块与应用层是密切相关的,而对于协议栈来说,应用层与kernel层的通信一般使用socket的ioctl来实现,因此就需要在linux vlan 模块实现socket ioctl相关的接口函数,以实现vlan device的添加、删除、优先级的映射等,并在linux socket的ioctl中注册相应的接口函数
3. Linux vlan模块需要实现协议栈处理函数,包括数据包的接收处理以及发送处理等等
4. 作为一个成熟的功能模块,一定要提供debug手段,以实现对linux vlan device debug操作,目前一般是在proc文件系统中注册相应的目录与文件。
下面的内容就以这四个方面进行分析
2.1 struct net_device的接口函数分析
本节就分析一下主要的接口函数,而对于一些次要的接口函数,就不分析了。
2.1.1 vlan_dev_rebuild_header
功能:修改数据包的目的mac地址
主要用于发送方向第一次调用dev->hard_header时,由于目的ip地址没有解析
导致目的mac地址没有设置的情况,通过调用该函数实现设置数据包的
目的mac地址的功能。
int vlan_dev_rebuild_header(struct sk_buff *skb)
{
struct net_device *dev = skb->dev;
struct vlan_ethhdr *veth = (struct vlan_ethhdr *)(skb->data);
switch (veth->h_vlan_encapsulated_proto) {
#ifdef CONFIG_INET
case __constant_htons(ETH_P_IP):
/* TODO: Confirm this will work with VLAN headers... */
return arp_find(veth->h_dest, skb);
#endif
default:
printk(VLAN_DBG
"%s: unable to resolve type %X addresses.\n",
dev->name, ntohs(veth->h_vlan_encapsulated_proto));
memcpy(veth->h_source, dev->dev_addr, ETH_ALEN);
break;
};
return 0;
}
2.1.2 vlan_check_reorder_header
功能:移除数据包中的vlan头,即将数据包的vlan剥掉
static inline struct sk_buff *vlan_check_reorder_header(struct sk_buff *skb)
{
/*
只有vlan设备的模式为1时,才会执行剥除vlan头部的操作
flag的定义有如下两种,其中0x02表示使用vlan grvp协议,会与其他的交换机
动态的交换vlan信息。而0x1则不使用vlan grvp等协议,则在接收到vlan数据包
时将vlan剥除,发送数据包添加vlan头部。个人感觉一个vlan设备必须会设置
VLAN_FLAG_REORDER_HDR,可选是否设置VLAN_FLAG_GVRP。
VLAN_FLAG_REORDER_HDR = 0x1,
VLAN_FLAG_GVRP = 0x2,
*/
if (VLAN_DEV_INFO(skb->dev)->flags & 1) {
if (skb_shared(skb) || skb_cloned(skb)) {
struct sk_buff *nskb = skb_copy(skb, GFP_ATOMIC);
kfree_skb(skb);
skb = nskb;
}
if (skb) {
/* Lifted from Gleb's VLAN code... */
memmove(skb->data - ETH_HLEN,
skb->data - VLAN_ETH_HLEN, 12);
skb->mac.raw += VLAN_HLEN;
}
}
return skb;
}
2.1.3 vlan_dev_hard_heade
功能:创建报文的二层头部信息。
对于本地发送或者转发的数据,其二层的源mac、目的mac地址均
需要进行修改的。
1.对于支持reorder header的vlan device而言,此处仅需要增加增加二层头部即可,
不需要增加vlan tag,vlan tag是在vlan_dev_hard_start_xmit中添加
2.对于不支持reorder header的vlan device而言,此处在增加二层头部的同时,还
需要增加vlan tag,因为在vlan_dev_hard_start_xmit中,不会对该类vlan device增加vlan
tag 了。
疑问:如果按照上述的分析,那在vlan_dev_hard_start_xmit应该需要判断vlan_dev->flag
是否为1,若不为1,则说明数据包的vlan tag已经添加过了,就不需要再添加vlantag 了,但是在vlan_dev_hard_start_xmit中并没有对vlan_dev->flag的判断啊。
这是什么原因呢?
我们注意到在vlan_dev_hard_start_xmit中有判断数据包是否已存在vlan tag,若存在
则不再添加vlan tag,因为有了这个判断,所以就没有对vlan_dev->flag进行判断了。
其实vlan_dev_hard_start_xmit中仅仅判断是否带vlan tag来决定是否添加vlantag,本身是有问题的,即没法添加双层vlan tag了。其实我感觉此处可以修改一下,将判断修改如下:if (dev->flag &0x1 != 0) ,这样就可以处理多层vlan,也可以处理在vlan_dev_hard_header中已添加vlan的case。
int vlan_dev_hard_header(struct sk_buff *skb, struct net_device *dev,
unsigned short type, void *daddr, void *saddr,
unsigned len)
{
struct vlan_hdr *vhdr;
unsigned short veth_TCI = 0;
int rc = 0;
int build_vlan_header = 0;
struct net_device *vdev = dev; /* save this for the bottom of the method */
#ifdef VLAN_DEBUG
printk(VLAN_DBG "%s: skb: %p type: %hx len: %x vlan_id: %hx, daddr: %p\n",
__FUNCTION__, skb, type, len, VLAN_DEV_INFO(dev)->vlan_id, daddr);
#endif
/* build vlan header only if re_order_header flag is NOT set. This
* fixes some programs that get confused when they see a VLAN device
* sending a frame that is VLAN encoded (the consensus is that the VLAN
* device should look completely like an Ethernet device when the
* REORDER_HEADER flag is set) The drawback to this is some extra
* header shuffling in the hard_start_xmit. Users can turn off this
* REORDER behaviour with the vconfig tool.
*/
build_vlan_header = ((VLAN_DEV_INFO(dev)->flags & 1) == 0);
if (build_vlan_header) {
vhdr = (struct vlan_hdr *) skb_push(skb, VLAN_HLEN);
/* build the four bytes that make this a VLAN header. */
/* Now, construct the second two bytes. This field looks something
* like:
* usr_priority: 3 bits (high bits)
* CFI 1 bit
* VLAN ID 12 bits (low bits)
*
*/
veth_TCI = VLAN_DEV_INFO(dev)->vlan_id;
veth_TCI |= vlan_dev_get_egress_qos_mask(dev, skb);
vhdr->h_vlan_TCI = htons(veth_TCI);
/*
* Set the protocol type.
* For a packet of type ETH_P_802_3 we put the length in here instead.
* It is up to the 802.2 layer to carry protocol information.
*/
if (type != ETH_P_802_3) {
vhdr->h_vlan_encapsulated_proto = htons(type);
} else {
vhdr->h_vlan_encapsulated_proto = htons(len);
}
skb->protocol = htons(ETH_P_8021Q);
skb->nh.raw = skb->data;
}
/* Before delegating work to the lower layer, enter our MAC-address */
if (saddr == NULL)
saddr = dev->dev_addr;
dev = VLAN_DEV_INFO(dev)->real_dev;
/* MPLS can send us skbuffs w/out enough space. This check will grow the
* skb if it doesn't have enough headroom. Not a beautiful solution, so
* I'll tick a counter so that users can know it's happening... If they
* care...
*/
/* NOTE: This may still break if the underlying device is not the final
* device (and thus there are more headers to add...) It should work for
* good-ole-ethernet though.
*/
if (skb_headroom(skb) < dev->hard_header_len) {
struct sk_buff *sk_tmp = skb;
skb = skb_realloc_headroom(sk_tmp, dev->hard_header_len);
kfree_skb(sk_tmp);
if (skb == NULL) {
struct net_device_stats *stats = vlan_dev_get_stats(vdev);
stats->tx_dropped++;
return -ENOMEM;
}
VLAN_DEV_INFO(vdev)->cnt_inc_headroom_on_tx++;
#ifdef VLAN_DEBUG
printk(VLAN_DBG "%s: %s: had to grow skb.\n", __FUNCTION__, vdev->name);
#endif
}
if (build_vlan_header) {
/* Now make the underlying real hard header */
rc = dev->hard_header(skb, dev, ETH_P_8021Q, daddr, saddr, len + VLAN_HLEN);
if (rc > 0) {
rc += VLAN_HLEN;
} else if (rc < 0) {
rc -= VLAN_HLEN;
}
} else {
/* If here, then we'll just make a normal looking ethernet frame,
* but, the hard_start_xmit method will insert the tag (it has to
* be able to do this for bridged and other skbs that don't come
* down the protocol stack in an orderly manner.
*/
rc = dev->hard_header(skb, dev, type, daddr, saddr, len);
}
return rc;
}
2.1. 4 vlan_dev_hard_start_xmit
功能:vlan device的hard_start_xmit函数(real dev对应的网口不支持硬件vlan的hard_start_xmit函数)
1.当数据包没有携带vlan时,则根据数据包的priority、vlan device的vlanid,组建vlan值,
并添加到数据包中
2.增加统计计数,修改数据包的dev指向real device
3.调用dev_queue_xmit,让数据从real device对应的网卡发送出去。
通过该函数可以看出,linux仅对untag的数据进行添加vlan 的操作,当数据包以及
包含vlan时,则不会再进行添加vlan的操作。因此若想要支持双层vlan,还是需要
修改linux的vlan架构的。
int vlan_dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct net_device_stats *stats = vlan_dev_get_stats(dev);
struct vlan_ethhdr *veth = (struct vlan_ethhdr *)(skb->data);
/* Handle non-VLAN frames if they are sent to us, for example by DHCP.
*
* NOTE: THIS ASSUMES DIX ETHERNET, SPECIFICALLY NOT SUPPORTING
* OTHER THINGS LIKE FDDI/TokenRing/802.3 SNAPs...
*/
if (veth->h_vlan_proto != __constant_htons(ETH_P_8021Q)) {
int orig_headroom = skb_headroom(skb);
unsigned short veth_TCI;
/* This is not a VLAN frame...but we can fix that! */
VLAN_DEV_INFO(dev)->cnt_encap_on_xmit++;
#ifdef VLAN_DEBUG
printk(VLAN_DBG "%s: proto to encap: 0x%hx (hbo)\n",
__FUNCTION__, htons(veth->h_vlan_proto));
#endif
/* Construct the second two bytes. This field looks something
* like:
* usr_priority: 3 bits (high bits)
* CFI 1 bit
* VLAN ID 12 bits (low bits)
*/
veth_TCI = VLAN_DEV_INFO(dev)->vlan_id;
veth_TCI |= vlan_dev_get_egress_qos_mask(dev, skb);
skb = __vlan_put_tag(skb, veth_TCI);
if (!skb) {
stats->tx_dropped++;
return 0;
}
if (orig_headroom < VLAN_HLEN) {
VLAN_DEV_INFO(dev)->cnt_inc_headroom_on_tx++;
}
}
#ifdef VLAN_DEBUG
printk(VLAN_DBG "%s: about to send skb: %p to dev: %s\n",
__FUNCTION__, skb, skb->dev->name);
printk(VLAN_DBG " %2hx.%2hx.%2hx.%2xh.%2hx.%2hx %2hx.%2hx.%2hx.%2hx.%2hx.%2hx %4hx %4hx %4hx\n",
veth->h_dest[0], veth->h_dest[1], veth->h_dest[2], veth->h_dest[3], veth->h_dest[4], veth->h_dest[5],
veth->h_source[0], veth->h_source[1], veth->h_source[2], veth->h_source[3], veth->h_source[4], veth->h_source[5],
veth->h_vlan_proto, veth->h_vlan_TCI, veth->h_vlan_encapsulated_proto);
#endif
stats->tx_packets++; /* for statics only */
stats->tx_bytes += skb->len;
skb->dev = VLAN_DEV_INFO(dev)->real_dev;
dev_queue_xmit(skb);
return 0;
}
2.1.5 vlan_dev_change_mtu
/*
功能:修改vlan device的mtu值
*/
int vlan_dev_change_mtu(struct net_device *dev, int new_mtu)
{
/* TODO: gotta make sure the underlying layer can handle it,
* maybe an IFF_VLAN_CAPABLE flag for devices?
*/
if (VLAN_DEV_INFO(dev)->real_dev->mtu < new_mtu)
return -ERANGE;
dev->mtu = new_mtu;
return 0;
}
2.1.6 register_vlan_device
该函数实现注册一个vlan device设备,并实现对struct net_device的相应接口函数进行赋值,实现struct net_device的接口函数指向上述2.1.1-2.1.5中介绍的函数,通过该函数就真正的注册了一个vlan device设备,其二层发送函数就完全是vlan的处理了。这个函数还是比较重要的。
static struct net_device *register_vlan_device(const char *eth_IF_name,
unsigned short VLAN_ID)
{
struct vlan_group *grp;
struct net_device *new_dev;
struct net_device *real_dev; /* the ethernet device */
char name[IFNAMSIZ];
int i;
#ifdef VLAN_DEBUG
printk(VLAN_DBG "%s: if_name -:%s:- vid: %i\n",
__FUNCTION__, eth_IF_name, VLAN_ID);
#endif
if (VLAN_ID >= VLAN_VID_MASK)
goto out_ret_null;
/* find the device relating to eth_IF_name. */
real_dev = dev_get_by_name(eth_IF_name);
if (!real_dev)
goto out_ret_null;
if (real_dev->features & NETIF_F_VLAN_CHALLENGED) {
printk(VLAN_DBG "%s: VLANs not supported on %s.\n",
__FUNCTION__, real_dev->name);
goto out_put_dev;
}
if ((real_dev->features & NETIF_F_HW_VLAN_RX) &&
(real_dev->vlan_rx_register == NULL ||
real_dev->vlan_rx_kill_vid == NULL)) {
printk(VLAN_DBG "%s: Device %s has buggy VLAN hw accel.\n",
__FUNCTION__, real_dev->name);
goto out_put_dev;
}
if ((real_dev->features & NETIF_F_HW_VLAN_FILTER) &&
(real_dev->vlan_rx_add_vid == NULL ||
real_dev->vlan_rx_kill_vid == NULL)) {
printk(VLAN_DBG "%s: Device %s has buggy VLAN hw accel.\n",
__FUNCTION__, real_dev->name);
goto out_put_dev;
}
/* From this point on, all the data structures must remain
* consistent.
*/
rtnl_lock();
/* The real device must be up and operating in order to
* assosciate a VLAN device with it.
*/
if (!(real_dev->flags & IFF_UP))
goto out_unlock;
/*
若在该real device中,已经创建了该vid相关的vlan device,则返回
失败
*/
if (__find_vlan_dev(real_dev, VLAN_ID) != NULL) {
/* was already registered. */
printk(VLAN_DBG "%s: ALREADY had VLAN registered\n", __FUNCTION__);
goto out_unlock;
}
/* Gotta set up the fields for the device. */
#ifdef VLAN_DEBUG
printk(VLAN_DBG "About to allocate name, vlan_name_type: %i\n",
vlan_name_type);
#endif
/*
根据vlan name的生成类型,生成相应的vlan device的名称
*/
switch (vlan_name_type) {
case VLAN_NAME_TYPE_RAW_PLUS_VID:
/* name will look like: eth1.0005 */
snprintf(name, IFNAMSIZ, "%s.%.4i", real_dev->name, VLAN_ID);
break;
case VLAN_NAME_TYPE_PLUS_VID_NO_PAD:
/* Put our vlan.VID in the name.
* Name will look like: vlan5
*/
snprintf(name, IFNAMSIZ, "vlan%i", VLAN_ID);
break;
case VLAN_NAME_TYPE_RAW_PLUS_VID_NO_PAD:
/* Put our vlan.VID in the name.
* Name will look like: eth0.5
*/
snprintf(name, IFNAMSIZ, "%s.%i", real_dev->name, VLAN_ID);
break;
case VLAN_NAME_TYPE_PLUS_VID:
/* Put our vlan.VID in the name.
* Name will look like: vlan0005
*/
default:
snprintf(name, IFNAMSIZ, "vlan%.4i", VLAN_ID);
};
/*
创建网络接口设备,并传递函数vlan_setup设置vlan device关联的接口函数指针
vlan_dev_change_mtu、vlan_dev_open、vlan_dev_stop、vlan_dev_set_mac_address、vlan_dev_ioctl等
*/
new_dev = alloc_netdev(sizeof(struct vlan_dev_info), name,
vlan_setup);
if (new_dev == NULL)
goto out_unlock;
#ifdef VLAN_DEBUG
printk(VLAN_DBG "Allocated new name -:%s:-\n", new_dev->name);
#endif
/* IFF_BROADCAST|IFF_MULTICAST; ??? */
/*初始化vlan device的flags、state、mtu、type、hard_header_len*/
new_dev->flags = real_dev->flags;
new_dev->flags &= ~IFF_UP;
new_dev->state = (real_dev->state & ((1<<__LINK_STATE_NOCARRIER) |
(1<<__LINK_STATE_DORMANT))) |
(1<<__LINK_STATE_PRESENT);
/* need 4 bytes for extra VLAN header info,
* hope the underlying device can handle it.
*/
new_dev->mtu = real_dev->mtu;
/* TODO: maybe just assign it to be ETHERNET? */
new_dev->type = real_dev->type;
new_dev->hard_header_len = real_dev->hard_header_len;
if (!(real_dev->features & NETIF_F_HW_VLAN_TX)) {
/* Regular ethernet + 4 bytes (18 total). */
new_dev->hard_header_len += VLAN_HLEN;
}
VLAN_MEM_DBG("new_dev->priv malloc, addr: %p size: %i\n",
new_dev->priv,
sizeof(struct vlan_dev_info));
memcpy(new_dev->broadcast, real_dev->broadcast, real_dev->addr_len);
memcpy(new_dev->dev_addr, real_dev->dev_addr, real_dev->addr_len);
new_dev->addr_len = real_dev->addr_len;
/*
根据real device是否支持NETIF_F_HW_VLAN_TX,让vlan device的hard_header、hard_start_xmit
、rebuild_header等函数指针指向不同的接口函数。
*/
if (real_dev->features & NETIF_F_HW_VLAN_TX) {
new_dev->hard_header = real_dev->hard_header;
new_dev->hard_start_xmit = vlan_dev_hwaccel_hard_start_xmit;
new_dev->rebuild_header = real_dev->rebuild_header;
} else {
new_dev->hard_header = vlan_dev_hard_header;
new_dev->hard_start_xmit = vlan_dev_hard_start_xmit;
new_dev->rebuild_header = vlan_dev_rebuild_header;
}
new_dev->hard_header_parse = real_dev->hard_header_parse;
/*设置该vlan device关联的vlan id、real device、flag*/
VLAN_DEV_INFO(new_dev)->vlan_id = VLAN_ID; /* 1 through VLAN_VID_MASK */
VLAN_DEV_INFO(new_dev)->real_dev = real_dev;
VLAN_DEV_INFO(new_dev)->dent = NULL;
VLAN_DEV_INFO(new_dev)->flags = 1;
#ifdef VLAN_DEBUG
printk(VLAN_DBG "About to go find the group for idx: %i\n",
real_dev->ifindex);
#endif
if (register_netdevice(new_dev))
goto out_free_newdev;
lockdep_set_class(&new_dev->_xmit_lock, &vlan_netdev_xmit_lock_key);
/*设置vlan device关联的real device的 index*/
new_dev->iflink = real_dev->ifindex;
vlan_transfer_operstate(real_dev, new_dev);
linkwatch_fire_event(new_dev); /* _MUST_ call rfc2863_policy() */
/* So, got the sucker initialized, now lets place
* it into our local structure.
*/
/*
判断该real device是否已经存在vlan group,若存在,则将vlan device存入grp->vlan_devices_arrays
中相应的数组中。
这个vlan_group变量实现了查找一个real device所关联的所有vlan device的操作。
由于vlan device、real device都是使用的struct net_device变量,因此目前代码中,没有将
vlan_group定义在net_device中,而是重新定义了一个全局的hash链表数组中。
那么问题来了,可不可以在struct net_device中增加一个链表头指针,将该real device
关联的vlan device设备都添加到该链表中呢?
当然是可以的,这样做的话,在查找一个real device关联的所有vlan device时,其查找
时间肯定比当前的查找时间要快的(因为不用进行hash相关的操作),但是每一个
struct net_device变量都增加了一个链表指针,增加了对内存的使用,这个就需要在
内存与性能之间做取舍了。
*/
grp = __vlan_find_group(real_dev->ifindex);
/* Note, we are running under the RTNL semaphore
* so it cannot "appear" on us.
*/
if (!grp) { /* need to add a new group */
grp = kzalloc(sizeof(struct vlan_group), GFP_KERNEL);
if (!grp)
goto out_free_unregister;
for (i=0; i < VLAN_GROUP_ARRAY_SPLIT_PARTS; i++) {
grp->vlan_devices_arrays[i] = kzalloc(
sizeof(struct net_device *)*VLAN_GROUP_ARRAY_PART_LEN,
GFP_KERNEL);
if (!grp->vlan_devices_arrays[i])
goto out_free_arrays;
}
/* printk(KERN_ALERT "VLAN REGISTER: Allocated new group.\n"); */
grp->real_dev_ifindex = real_dev->ifindex;
hlist_add_head_rcu(&grp->hlist,
&vlan_group_hash[vlan_grp_hashfn(real_dev->ifindex)]);
if (real_dev->features & NETIF_F_HW_VLAN_RX)
real_dev->vlan_rx_register(real_dev, grp);
}
vlan_group_set_device(grp, VLAN_ID, new_dev);
if (vlan_proc_add_dev(new_dev)<0)/* create it's proc entry */
printk(KERN_WARNING "VLAN: failed to add proc entry for %s\n",
new_dev->name);
if (real_dev->features & NETIF_F_HW_VLAN_FILTER)
real_dev->vlan_rx_add_vid(real_dev, VLAN_ID);
rtnl_unlock();
#ifdef VLAN_DEBUG
printk(VLAN_DBG "Allocated new device successfully, returning.\n");
#endif
return new_dev;
out_free_arrays:
vlan_group_free(grp);
out_free_unregister:
unregister_netdev(new_dev);
goto out_unlock;
out_free_newdev:
free_netdev(new_dev);
out_unlock:
rtnl_unlock();
out_put_dev:
dev_put(real_dev);
out_ret_null:
return NULL;
}
通过以上几个函数,就实现了一个vlan类型的struct net_device变量,创建了一个新的虚拟的网络接口。
2.2 Linux vlan模块与socket模块的关联
本小节分析下vlan模块在socket模块中注册的ioctl,以实现vlan接口的创建与删除等。
2.2.1 vlan_ioctl_set
该函数主要是为函数指针vlan_ioctl_hook赋值,而vlan_ioctl_hook则会socket的ioctl相关的命令下面,用于处理vlan相关的命令
void vlan_ioctl_set(int (*hook) (void __user *))
{
mutex_lock(&vlan_ioctl_mutex);
vlan_ioctl_hook = hook;
mutex_unlock(&vlan_ioctl_mutex);
}
在sock_ioctl函数里,针对命令SIOCGIFVLAN、SIOCSIFVLAN,则会调用vlan_ioctl_hook进行相应的处理。
而在vlan_proto_init函数里,通过vlan_ioctl_set(vlan_ioctl_handler),将vlan_ioctl_hook指向了函数vlan_ioctl_handler,这样对于socket的ioctl的SIOCGIFVLAN、SIOCSIFVLAN命令,即会调用函数数vlan_ioctl_handler进行处理
2.2.2 vlan_ioctl_handler
功能:vlan模块相关的ioctl接口函数
static int vlan_ioctl_handler(void __user *arg)
{
int err = 0;
unsigned short vid = 0;
struct vlan_ioctl_args args;
if (copy_from_user(&args, arg, sizeof(struct vlan_ioctl_args)))
return -EFAULT;
/* Null terminate this sucker, just in case. */
args.device1[23] = 0;
args.u.device2[23] = 0;
#ifdef VLAN_DEBUG
printk(VLAN_DBG "%s: args.cmd: %x\n", __FUNCTION__, args.cmd);
#endif
switch (args.cmd) {
case SET_VLAN_INGRESS_PRIORITY_CMD:
if (!capable(CAP_NET_ADMIN))
return -EPERM;
err = vlan_dev_set_ingress_priority(args.device1,
args.u.skb_priority,
args.vlan_qos);
break;
case SET_VLAN_EGRESS_PRIORITY_CMD:
if (!capable(CAP_NET_ADMIN))
return -EPERM;
err = vlan_dev_set_egress_priority(args.device1,
args.u.skb_priority,
args.vlan_qos);
break;
case SET_VLAN_FLAG_CMD:
if (!capable(CAP_NET_ADMIN))
return -EPERM;
err = vlan_dev_set_vlan_flag(args.device1,
args.u.flag,
args.vlan_qos);
break;
case SET_VLAN_NAME_TYPE_CMD:
if (!capable(CAP_NET_ADMIN))
return -EPERM;
if ((args.u.name_type >= 0) &&
(args.u.name_type < VLAN_NAME_TYPE_HIGHEST)) {
vlan_name_type = args.u.name_type;
err = 0;
} else {
err = -EINVAL;
}
break;
case ADD_VLAN_CMD:
if (!capable(CAP_NET_ADMIN))
return -EPERM;
/* we have been given the name of the Ethernet Device we want to
* talk to: args.dev1 We also have the
* VLAN ID: args.u.VID
*/
if (register_vlan_device(args.device1, args.u.VID)) {
err = 0;
} else {
err = -EINVAL;
}
break;
case DEL_VLAN_CMD:
if (!capable(CAP_NET_ADMIN))
return -EPERM;
/* Here, the args.dev1 is the actual VLAN we want
* to get rid of.
*/
err = unregister_vlan_device(args.device1);
break;
case GET_VLAN_INGRESS_PRIORITY_CMD:
/* TODO: Implement
err = vlan_dev_get_ingress_priority(args);
if (copy_to_user((void*)arg, &args,
sizeof(struct vlan_ioctl_args))) {
err = -EFAULT;
}
*/
err = -EINVAL;
break;
case GET_VLAN_EGRESS_PRIORITY_CMD:
/* TODO: Implement
err = vlan_dev_get_egress_priority(args.device1, &(args.args);
if (copy_to_user((void*)arg, &args,
sizeof(struct vlan_ioctl_args))) {
err = -EFAULT;
}
*/
err = -EINVAL;
break;
case GET_VLAN_REALDEV_NAME_CMD:
err = vlan_dev_get_realdev_name(args.device1, args.u.device2);
if (err)
goto out;
if (copy_to_user(arg, &args,
sizeof(struct vlan_ioctl_args))) {
err = -EFAULT;
}
break;
case GET_VLAN_VID_CMD:
err = vlan_dev_get_vid(args.device1, &vid);
if (err)
goto out;
args.u.VID = vid;
if (copy_to_user(arg, &args,
sizeof(struct vlan_ioctl_args))) {
err = -EFAULT;
}
break;
default:
/* pass on to underlying device instead?? */
printk(VLAN_DBG "%s: Unknown VLAN CMD: %x \n",
__FUNCTION__, args.cmd);
return -EINVAL;
};
out:
return err;
}
这个函数里,有几个函数比较重要,我们需要好好分析下,这几个函数为vlan_dev_set_vlan_flag、register_vlan_device、register_vlan_device、
vlan_dev_get_realdev_name、vlan_dev_get_vid、vlan_dev_set_ingress_priority、
vlan_dev_set_egress_priority
2.2.2.1vlan_dev_set_vlan_flag
/* Flags are defined in the vlan_dev_info class in include/linux/if_vlan.h file. */
/*
功能:设置vlan device的flag值,即是否支持reorder vlan header。
*/
int vlan_dev_set_vlan_flag(char *dev_name, __u32 flag, short flag_val)
{
struct net_device *dev = dev_get_by_name(dev_name);
if (dev) {
if (dev->priv_flags & IFF_802_1Q_VLAN) {
/* verify flag is supported */
if (flag == 1) {
if (flag_val) {
VLAN_DEV_INFO(dev)->flags |= 1;
} else {
VLAN_DEV_INFO(dev)->flags &= ~1;
}
dev_put(dev);
return 0;
} else {
printk(KERN_ERR "%s: flag %i is not valid.\n",
__FUNCTION__, (int)(flag));
dev_put(dev);
return -EINVAL;
}
} else {
printk(KERN_ERR
"%s: %s is not a vlan device, priv_flags: %hX.\n",
__FUNCTION__, dev->name, dev->priv_flags);
dev_put(dev);
}
} else {
printk(KERN_ERR "%s: Could not find device: %s\n",
__FUNCTION__, dev_name);
}
return -EINVAL;
}
2.2.2.2vlan_dev_set_ingress_priority
/*
设置vlan优先级与数据包优先级的map
*/
int vlan_dev_set_ingress_priority(char *dev_name, __u32 skb_prio, short vlan_prio)
{
struct net_device *dev = dev_get_by_name(dev_name);
if (dev) {
if (dev->priv_flags & IFF_802_1Q_VLAN) {
/* see if a priority mapping exists.. */
VLAN_DEV_INFO(dev)->ingress_priority_map[vlan_prio & 0x7] = skb_prio;
dev_put(dev);
return 0;
}
dev_put(dev);
}
return -EINVAL;
}
2.2.2.3 vlan_dev_set_egress_priority
/*
功能:设置输出数据包的qos与优先级之间的关联
*/
int vlan_dev_set_egress_priority(char *dev_name, __u32 skb_prio, short vlan_prio)
{
struct net_device *dev = dev_get_by_name(dev_name);
struct vlan_priority_tci_mapping *mp = NULL;
struct vlan_priority_tci_mapping *np;
if (dev) {
if (dev->priv_flags & IFF_802_1Q_VLAN) {
/* See if a priority mapping exists.. */
mp = VLAN_DEV_INFO(dev)->egress_priority_map[skb_prio & 0xF];
while (mp) {
if (mp->priority == skb_prio) {
mp->vlan_qos = ((vlan_prio << 13) & 0xE000);
dev_put(dev);
return 0;
}
mp = mp->next;
}
/* Create a new mapping then. */
mp = VLAN_DEV_INFO(dev)->egress_priority_map[skb_prio & 0xF];
np = kmalloc(sizeof(struct vlan_priority_tci_mapping), GFP_KERNEL);
if (np) {
np->next = mp;
np->priority = skb_prio;
np->vlan_qos = ((vlan_prio << 13) & 0xE000);
VLAN_DEV_INFO(dev)->egress_priority_map[skb_prio & 0xF] = np;
dev_put(dev);
return 0;
} else {
dev_put(dev);
return -ENOBUFS;
}
}
dev_put(dev);
}
return -EINVAL;
}
2.2.2.4 register_vlan_device
这个函数在2.1.6中以及介绍过了,此处就不分析了。
此处就分析一下该函数调用的几个函数吧。
功能:根据real_dev、vid,查找依附于real_dev上的vlan device
struct net_device *__find_vlan_dev(struct net_device *real_dev,
unsigned short VID)
{
struct vlan_group *grp = __vlan_find_group(real_dev->ifindex);
if (grp)
return vlan_group_get_device(grp, VID);
return NULL;
}
/*
功能:设置vlan device关联的接口函数指针
*/
static void vlan_setup(struct net_device *new_dev)
{
SET_MODULE_OWNER(new_dev);
/* new_dev->ifindex = 0; it will be set when added to
* the global list.
* iflink is set as well.
*/
new_dev->get_stats = vlan_dev_get_stats;
/* Make this thing known as a VLAN device */
new_dev->priv_flags |= IFF_802_1Q_VLAN;
/* Set us up to have no queue, as the underlying Hardware device
* can do all the queueing we could want.
*/
new_dev->tx_queue_len = 0;
/* set up method calls */
new_dev->change_mtu = vlan_dev_change_mtu;
new_dev->open = vlan_dev_open;
new_dev->stop = vlan_dev_stop;
new_dev->set_mac_address = vlan_dev_set_mac_address;
new_dev->set_multicast_list = vlan_dev_set_multicast_list;
new_dev->destructor = free_netdev;
new_dev->do_ioctl = vlan_dev_ioctl;
}
/*
功能:根据real device 的index,查找与该real device关联的vlan group变量
*/
static struct vlan_group *__vlan_find_group(int real_dev_ifindex)
{
struct vlan_group *grp;
struct hlist_node *n;
int hash = vlan_grp_hashfn(real_dev_ifindex);
hlist_for_each_entry_rcu(grp, n, &vlan_group_hash[hash], hlist) {
if (grp->real_dev_ifindex == real_dev_ifindex)
return grp;
}
return NULL;
}
/*
功能:根据vlan id 以及vlan group变量,在vlan_devices_arrays中增加相应的vlan设备
由于是直接增加设备,没有进行存在性判断,因此在调用该函数
之前,应该调用vlan_group_get_device判断要添加的vlan设备是否已存在。
*/
static inline void vlan_group_set_device(struct vlan_group *vg, int vlan_id,
struct net_device *dev)
{
struct net_device **array;
if (!vg)
return;
array = vg->vlan_devices_arrays[vlan_id / VLAN_GROUP_ARRAY_PART_LEN];
array[vlan_id % VLAN_GROUP_ARRAY_PART_LEN] = dev;
}
2.2.2.5 vlan_dev_get_realdev_name
功能:根据vlan device的名称,获取其所依附的real device的名称。
int vlan_dev_get_realdev_name(const char *dev_name, char* result)
{
struct net_device *dev = dev_get_by_name(dev_name);
int rv = 0;
if (dev) {
if (dev->priv_flags & IFF_802_1Q_VLAN) {
strncpy(result, VLAN_DEV_INFO(dev)->real_dev->name, 23);
rv = 0;
} else {
rv = -EINVAL;
}
dev_put(dev);
} else {
rv = -ENODEV;
}
return rv;
}
2.2.2.6 vlan_dev_get_vid
功能:查找vlan设备关联的vlan id
int vlan_dev_get_vid(const char *dev_name, unsigned short* result)
{
struct net_device *dev = dev_get_by_name(dev_name);
int rv = 0;
if (dev) {
if (dev->priv_flags & IFF_802_1Q_VLAN) {
*result = VLAN_DEV_INFO(dev)->vlan_id;
rv = 0;
} else {
rv = -EINVAL;
}
dev_put(dev);
} else {
rv = -ENODEV;
}
return rv;
}
至此将linux vlan模块与socket的关系分析完了。
2.3 Linux vlan模块与协议栈处理函数的关联
既然新开发一个vlan的功能模块,自然需要融入当前的协议栈代码空间中。对于数据流来说也就tx、rx两个方向罢了,tx方向就是vlan device的hard_start_xmit函数,只要在注册struct net_device变量时,对其hard_start_xmit指针进行赋值即可,这个赋值操作在register_vlan_device中已经实现了,且vlan_dev_hard_start_xmit函数也在2.1.4中分析过了。
那还需要考虑接收方向的vlan处理函数了。
那对于vlan的处理放在哪里比较合适呢?
如果按协议栈来说的话,那因为vlan头部在二层mac头部后面,如果把其作为一个三层协议对待,像ipv4、ipv6那样,在三层协议相关的回调函数链表中,增加vlan接收处理函数的链表成员即可。
但是,这样做虽然可以正常的剥掉vlan头部,但是vlan协议并不是严格意义上的三层协议啊,如果按这样考虑的话,则把vlan接收处理函数放在netif_receive_skb中进行调用反而会好一点。
那该选用哪一个方式呢?其实上面两种方式也就是linux2.6.x与linux3.x的实现方式。
在linx 2.6的内核里,是通过将dev_add_pack将该接收函数注册到三层协议相关的接收函数的链表里的。即把vlan的接收函数与ip 、ipv6等协议的接收函数注册到同一个链表里的。
但是考虑到vlan毕竟是属于二层协议的范畴,因此在linux3.x中,对剥除vlan tag的操作进行了调整,即在netif_receive_skb中,即调用vlan_untag操作,剥除数据包的vlan tag,接着调用vlan_do_receive修改skb->dev的值,接着重新返回到vlan_untag的起始调用处,即实现了从real_dev->vlan_dev的转换。这样既将vlan的剥除与三层协议相关的接收函数区别开来,又省去了netif_rx的调用。
对于linux2.6.x与linux3.xvlan模块的实现差别,再多说一些;
对于linux3.x以上版本的vlan功能模块,在数据包从vlan dev发送出去时,仅仅设置skb->vlan_tci的值,而不在数据包中增加vlan头部信息。增加vlan头部的操作,是在将skb->dev设置为real dev后,在dev_hard_start_xmit中根据skb->vlan_tci的值来决定是否添加vlan 头部。而linux 3.x以下的版本,则是在vlan dev的dev->netdev_ops->ndo_start_xmit进行vlan tag的添加,然后才修改skb->dev的值为real dev。
在linux 3.x 以下,接收方向是在将skb->dev的值修改为vlan dev后,才剥除vlan tag的;发送方向,则是在添加vlan tag后,才修改skb->dev的值为real dev。
在linux 3.x以上,接收方向的数据包,只要有vlan tag,则会把vlan tag剥除掉,之后再将skb->dev修改成vlan dev;发送方向,则是在将skb->dev的值修改为real dev后,再添加 vlan tag。
一个是在vlan dev处进行vlan tag的剥除与添加,一个策略是在real dev处进行vlan tag的剥除与添加。
本文是基于linux2.6.21 内核进行分析的,因此还是把vlan作为三层协议注册接收函数的方式。下面分析一下函数vlan_skb_recv。
2.3.1 vlan_skb_recv
vlan设备的接收处理函数。
功能:将数据包携带的vlan头部剥除掉
int vlan_skb_recv(struct sk_buff *skb, struct net_device *dev,
struct packet_type* ptype, struct net_device *orig_dev)
{
unsigned char *rawp = NULL;
struct vlan_hdr *vhdr = (struct vlan_hdr *)(skb->data);
unsigned short vid;
struct net_device_stats *stats;
unsigned short vlan_TCI;
__be16 proto;
/* vlan_TCI = ntohs(get_unaligned(&vhdr->h_vlan_TCI)); */
vlan_TCI = ntohs(vhdr->h_vlan_TCI);
vid = (vlan_TCI & VLAN_VID_MASK);
#ifdef VLAN_DEBUG
printk(VLAN_DBG "%s: skb: %p vlan_id: %hx\n",
__FUNCTION__, skb, vid);
#endif
/* Ok, we will find the correct VLAN device, strip the header,
* and then go on as usual.
*/
/* We have 12 bits of vlan ID.
*
* We must not drop allow preempt until we hold a
* reference to the device (netif_rx does that) or we
* fail.
*/
rcu_read_lock();
/*修改数据包的dev指针,再次调用netif_rx函数,以实现
数据包从real_dev->vlan_dev的流程,以实现逻辑意义上的
数据包在两个网口间的传递*/
skb->dev = __find_vlan_dev(dev, vid);
if (!skb->dev) {
rcu_read_unlock();
#ifdef VLAN_DEBUG
printk(VLAN_DBG "%s: ERROR: No net_device for VID: %i on dev: %s [%i]\n",
__FUNCTION__, (unsigned int)(vid), dev->name, dev->ifindex);
#endif
kfree_skb(skb);
return -1;
}
skb->dev->last_rx = jiffies;
/* Bump the rx counters for the VLAN device. */
/*
增加vlan设备的rx 统计计数
*/
stats = vlan_dev_get_stats(skb->dev);
stats->rx_packets++;
stats->rx_bytes += skb->len;
/* Take off the VLAN header (4 bytes currently) */
/*skb->data指针后移4个字节*/
skb_pull_rcsum(skb, VLAN_HLEN);
/* Ok, lets check to make sure the device (dev) we
* came in on is what this VLAN is attached to.
*/
/*若接收数据包的网络设备与vlan设备所依附的真实设备
不匹配,则释放该数据包占用的内存,并返回错误*/
if (dev != VLAN_DEV_INFO(skb->dev)->real_dev) {
rcu_read_unlock();
#ifdef VLAN_DEBUG
printk(VLAN_DBG "%s: dropping skb: %p because came in on wrong device, dev: %s real_dev: %s, skb_dev: %s\n",
__FUNCTION__, skb, dev->name,
VLAN_DEV_INFO(skb->dev)->real_dev->name,
skb->dev->name);
#endif
kfree_skb(skb);
stats->rx_errors++;
return -1;
}
/*
* Deal with ingress priority mapping.
*/
skb->priority = vlan_get_ingress_priority(skb->dev, ntohs(vhdr->h_vlan_TCI));
#ifdef VLAN_DEBUG
printk(VLAN_DBG "%s: priority: %lu for TCI: %hu (hbo)\n",
__FUNCTION__, (unsigned long)(skb->priority),
ntohs(vhdr->h_vlan_TCI));
#endif
/* The ethernet driver already did the pkt_type calculations
* for us...
*/
switch (skb->pkt_type) {
case PACKET_BROADCAST: /* Yeah, stats collect these together.. */
// stats->broadcast ++; // no such counter :-(
break;
case PACKET_MULTICAST:
stats->multicast++;
break;
case PACKET_OTHERHOST:
/* Our lower layer thinks this is not local, let's make sure.
* This allows the VLAN to have a different MAC than the underlying
* device, and still route correctly.
*/
if (!compare_ether_addr(eth_hdr(skb)->h_dest, skb->dev->dev_addr)) {
/* It is for our (changed) MAC-address! */
skb->pkt_type = PACKET_HOST;
}
break;
default:
break;
};
/* Was a VLAN packet, grab the encapsulated protocol, which the layer
* three protocols care about.
*/
/* proto = get_unaligned(&vhdr->h_vlan_encapsulated_proto); */
proto = vhdr->h_vlan_encapsulated_proto;
skb->protocol = proto;
if (ntohs(proto) >= 1536) {
/* place it back on the queue to be handled by
* true layer 3 protocols.
*/
/* See if we are configured to re-write the VLAN header
* to make it look like ethernet...
*/
skb = vlan_check_reorder_header(skb);
/* Can be null if skb-clone fails when re-ordering */
if (skb) {
netif_rx(skb);
} else {
/* TODO: Add a more specific counter here. */
stats->rx_errors++;
}
rcu_read_unlock();
return 0;
}
rawp = skb->data;
/*
* This is a magic hack to spot IPX packets. Older Novell breaks
* the protocol design and runs IPX over 802.3 without an 802.2 LLC
* layer. We look for FFFF which isn't a used 802.2 SSAP/DSAP. This
* won't work for fault tolerant netware but does for the rest.
*/
if (*(unsigned short *)rawp == 0xFFFF) {
skb->protocol = __constant_htons(ETH_P_802_3);
/* place it back on the queue to be handled by true layer 3 protocols.
*/
/* See if we are configured to re-write the VLAN header
* to make it look like ethernet...
*/
skb = vlan_check_reorder_header(skb);
/* Can be null if skb-clone fails when re-ordering */
if (skb) {
netif_rx(skb);
} else {
/* TODO: Add a more specific counter here. */
stats->rx_errors++;
}
rcu_read_unlock();
return 0;
}
/*
* Real 802.2 LLC
*/
skb->protocol = __constant_htons(ETH_P_802_2);
/* place it back on the queue to be handled by upper layer protocols.
*/
/* See if we are configured to re-write the VLAN header
* to make it look like ethernet...
*/
skb = vlan_check_reorder_header(skb);
/* Can be null if skb-clone fails when re-ordering */
if (skb) {
netif_rx(skb);
} else {
/* TODO: Add a more specific counter here. */
stats->rx_errors++;
}
rcu_read_unlock();
return 0;
}
而该函数的注册,则是通过以下代码段实现的:
三层协议接收函数注册相关的结构体,此处为注册vlan的接收处理函数的回调函数
static struct packet_type vlan_packet_type = {
.type = __constant_htons(ETH_P_8021Q),
.func = vlan_skb_recv, /* VLAN receive method */
};
dev_add_pack(&vlan_packet_type);
关于dev_add_pack的分析,可以看下先前的一个分析文档:linux3、4层接收函数的注册分析
至此将几个主要的部分都分析完了,接着就分析仪器vlan模块的初始化函数vlan_proto_init
三、vlan模块的初始化
3.1 vlan_proto_init
vlan协议相关初始化
1.在proc文件系统下注册vlan相关的文件目录
2.在通知链netdev_chain注册vlan相关的回调处理函数
3.在三层协议接收处理函数相关的注册链表中添加vlan的注册
以实现对接收到的vlan数据包的处理。
4.调用 vlan_ioctl_set将vlan模块相关的ioctl接口函数注册到socket中,在socket中增加
相应的ioctl命令,实现vlan模块与socket模块的关联。
static int __init vlan_proto_init(void)
{
int err;
printk(VLAN_INF "%s v%s %s\n",
vlan_fullname, vlan_version, vlan_copyright);
printk(VLAN_INF "All bugs added by %s\n",
vlan_buggyright);
/* proc file system initialization */
err = vlan_proc_init();
if (err < 0) {
printk(KERN_ERR
"%s %s: can't create entry in proc filesystem!\n",
__FUNCTION__, VLAN_NAME);
return err;
}
dev_add_pack(&vlan_packet_type);
/* Register us to receive netdevice events */
err = register_netdevice_notifier(&vlan_notifier_block);
if (err < 0) {
dev_remove_pack(&vlan_packet_type);
vlan_proc_cleanup();
return err;
}
vlan_ioctl_set(vlan_ioctl_handler);
return 0;
}
至此,就算是把vlan模块分析完了,可能还有些地方分析的不准确,后续如果有更深入的内容,会进行补充的。