本文代码基于linux2.6.21
Vlan即虚拟局域网,一个vlan能够模拟一个常规的交换网络,实现了将一个物理的交换机划分成多个逻辑的交换网络。而不同的vlan之间如果要进行通信就要通过三层协议来实现。
在linux中vlan的配置使用vconfig,使用vconfig配置一个交换网络,大致的流程如下:
#
#vconfigaddeth0100
#
#ifconfigeth0.100up
#brctladdbrbr1
#ifconfigbr1up
#brctladdifbr1eth0.100
#brctladdifbr1eth2
这样就实现了将从eth0口进来的vlanid为100的数据流与eth2放在了一个逻辑交换网络中。
下面开始分析linuxvlan模块
一、相关的数据结构
1.1structvlan_ethhdr
包含vlan头部的二层头部结构体
structvlan_ethhdr{
unsignedchar h_dest[ETH_ALEN]; /*destinationethaddr */
unsignedchar h_source[ETH_ALEN]; /*sourceetheraddr */
__be16h_vlan_proto;/*Shouldalwaysbe0x8100*/
__be16h_vlan_TCI;/*EncapsulatespriorityandVLANID*/
__be16 h_vlan_encapsulated_proto;/*packettypeIDfield(orlen)*/
};
1.2structvlan_hdr
vlan头部关联的结构体
structvlan_hdr{
__be16h_vlan_TCI;/*EncapsulatespriorityandVLANID*/
__be16h_vlan_encapsulated_proto;/*packettypeIDfield(orlen)*/
};
1.3structvlan_group
该结构体主要是实现将一个realdevice关联的vlandevice设备存放在vlan_devices_arrays,
(该变量首先是一个数组,数组里的每一个指针都是一个二级指针)
structvlan_group{
/*realdevice的index*/
intreal_dev_ifindex;/*Theifindexoftheethernet(like)devicethevlanisattachedto.*/
structhlist_node hlist; /*linkedlist*/
structnet_device**vlan_devices_arrays[VLAN_GROUP_ARRAY_SPLIT_PARTS];
structrcu_head rcu;
};
该变量也是比较重要的一个数据结构,可以保证一个realdevice针对每一个vlanid创建的vlandevice都是唯一的。
Vlangroup是通过全局变量vlan_group_hash的hash链表链接在一起的,其定义如下:
staticstructhlist_headvlan_group_hash[VLAN_GRP_HASH_SIZE];
由于vlandevice、realdevice都是使用的structnet_device变量,因此目前代码中,没有将vlan_group定义在net_device中,而是重新定义了一个全局的hash链表数组中。
那么问题来了,可不可以在structnet_device中增加一个链表头指针,将该realdevice
关联的vlandevice设备都添加到该链表中呢?
当然是可以的,这样做的话,在查找一个realdevice关联的所有vlandevice时,其查找
时间肯定比当前的查找时间要快的(因为不用进行hash相关的操作),但是每一个structnet_device变量都增加了一个链表指针,增加了对内存的使用,这个就需要在内存与性能之间做取舍了。
相应的set与get的接口函数如下:
/*
功能:根据vlanid以及vlan_group变量,查找符合条件的vlandevice
*/
staticinlinestructnet_device*vlan_group_get_device(structvlan_group*vg,intvlan_id)
{
structnet_device**array;
array=vg->vlan_devices_arrays[vlan_id/VLAN_GROUP_ARRAY_PART_LEN];
returnarray[vlan_id%VLAN_GROUP_ARRAY_PART_LEN];
}
/*
功能:根据vlanid以及vlangroup变量,在vlan_devices_arrays中增加相应的vlan设备
由于是直接增加设备,没有进行存在性判断,因此在调用该函数
之前,应该调用vlan_group_get_device判断要添加的vlan设备是否已存在。
*/
staticinlinevoidvlan_group_set_device(structvlan_group*vg,intvlan_id,
structnet_device*dev)
{
structnet_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.4structvlan_dev_info
vlan设备相关的信息结构体
该结构体定义了vlandevice相关的一些信息,包括输入/输出方向优先级映射、
vlanid、关联的realdevice、性能统计相关的信息等。
该结构相关的变量被定义在structnet_device的priv指针变量指向的内存空间中。
#defineVLAN_DEV_INFO(x)((structvlan_dev_info*)(x->priv))
structvlan_dev_info{
/**Thiswillbethemappingthatcorrelatesskb->priorityto
*3bitsofVLANQOStags…
*/
/*
输入数据包的优先级与vlan优先级的map
*/
unsignedlongingress_priority_map[8];
structvlan_priority_tci_mapping*egress_priority_map[16];/*hashtable*/
/*vlanid*/
unsignedshortvlan_id;/*TheVLANIdentifierforthisinterface.*/
/*
vlandevice是否支持reorderheader,即rx方向,实现剥除掉数据包的vlan操作
*/
unsignedshortflags;/*(1<<0)re_order_headerThisoptionwillcausethe
*VLANcodetomovearoundtheethernetheaderon
*ingresstomaketheskblook**exactly**likeit
*cameinfromanethernetport.Thisdestroyssomeof
*theVLANinformationintheskb,butitfixesprograms
*likeDHCPthatusepacket-filteringanddon’tunderstand
*802.1Q
*/
/*
以下三个参数是与组播相关的
*/
structdev_mc_list*old_mc_list;/*oldmulti-castlistfortheVLANinterface..
*wesavethissowecantellwhatchangeswere
*made,inordertofeedtherightchangesdown
*totherealhardware…
*/
intold_allmulti;/*similartoabove.*/
intold_promiscuity;/*similartoabove.*/
/*
vlan设备所依附的真实设备
*/
structnet_device*real_dev;/*theunderlyingdevice/interface*/
/*
proc文件系统相关的参数
*/
structproc_dir_entry*dent;/*Holdstheprocdata*/
unsignedlongcnt_inc_headroom_on_tx;/*HowmanytimesdidwehavetogrowtheskbonTX.*/
unsignedlongcnt_encap_on_xmit;/*HowmanytimesdidwehavetoencapsulatetheskbonTX.*/
/*
vlan设备相关联的
*/
structnet_device_statsdev_stats;/*Devicestats(rx-bytes,tx-pkts,etc…)*/
};
以上就是主要相关的数据结构,下面分析下相应的功能。
二、linuxvlan架构分析
对于linuxvlan架构来说,主要分为四个方面。
1.Structnet_device中相应接口函数的实现,既然vlandevice使用的也是structnet_device变量,因此针对vlandevice就需要实现相应的接口函数,包括vlan_dev_hard_header、vlan_dev_hard_start_xmit、vlan_dev_open、vlan_dev_stop
2.Linuxvlan模块与socket模块的关联,因为linuxvlan模块与应用层是密切相关的,而对于协议栈来说,应用层与kernel层的通信一般使用socket的ioctl来实现,因此就需要在linuxvlan模块实现socketioctl相关的接口函数,以实现vlandevice的添加、删除、优先级的映射等,并在linuxsocket的ioctl中注册相应的接口函数
3.Linuxvlan模块需要实现协议栈处理函数,包括数据包的接收处理以及发送处理等等
4.作为一个成熟的功能模块,一定要提供debug手段,以实现对linuxvlandevicedebug操作,目前一般是在proc文件系统中注册相应的目录与文件。
下面的内容就以这四个方面进行分析
2.1structnet_device的接口函数分析
本节就分析一下主要的接口函数,而对于一些次要的接口函数,就不分析了。
2.1.1vlan_dev_rebuild_header
功能:修改数据包的目的mac地址
主要用于发送方向第一次调用dev->hard_header时,由于目的ip地址没有解析
导致目的mac地址没有设置的情况,通过调用该函数实现设置数据包的
目的mac地址的功能。
intvlan_dev_rebuild_header(structsk_buff*skb)
{
structnet_device*dev=skb->dev;
structvlan_ethhdr*veth=(structvlan_ethhdr*)(skb->data);
switch(veth->h_vlan_encapsulated_proto){
#ifdefCONFIG_INET
case__constant_htons(ETH_P_IP):
/*TODO:ConfirmthiswillworkwithVLANheaders…*/
returnarp_find(veth->h_dest,skb);
#endif
default:
printk(VLAN_DBG
"%s:unabletoresolvetype%Xaddresses.\n",
dev->name,ntohs(veth->h_vlan_encapsulated_proto));
memcpy(veth->h_source,dev->dev_addr,ETH_ALEN);
break;
};
return0;
}
2.1.2vlan_check_reorder_header
功能:移除数据包中的vlan头,即将数据包的vlan剥掉
staticinlinestructsk_buff*vlan_check_reorder_header(structsk_buff*skb)
{
/*
只有vlan设备的模式为1时,才会执行剥除vlan头部的操作
flag的定义有如下两种,其中0x02表示使用vlangrvp协议,会与其他的交换机
动态的交换vlan信息。而0x1则不使用vlangrvp等协议,则在接收到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)){
structsk_buff*nskb=skb_copy(skb,GFP_ATOMIC);
kfree_skb(skb);
skb=nskb;
}
if(skb){
/*LiftedfromGleb’sVLANcode…*/
memmove(skb->data-ETH_HLEN,
skb->data-VLAN_ETH_HLEN,12);
skb->mac.raw+=VLAN_HLEN;
}
}
returnskb;
}
2.1.3vlan_dev_hard_heade
功能:创建报文的二层头部信息。
对于本地发送或者转发的数据,其二层的源mac、目的mac地址均
需要进行修改的。
1.对于支持reorderheader的vlandevice而言,此处仅需要增加增加二层头部即可,
不需要增加vlantag,vlantag是在vlan_dev_hard_start_xmit中添加
2.对于不支持reorderheader的vlandevice而言,此处在增加二层头部的同时,还
需要增加vlantag,因为在vlan_dev_hard_start_xmit中,不会对该类vlandevice增加vlan
tag了。
疑问:如果按照上述的分析,那在vlan_dev_hard_start_xmit应该需要判断vlan_dev->flag
是否为1,若不为1,则说明数据包的vlantag已经添加过了,就不需要再添加vlantag 了,但是在vlan_dev_hard_start_xmit中并没有对vlan_dev->flag的判断啊。
这是什么原因呢?
我们注意到在vlan_dev_hard_start_xmit中有判断数据包是否已存在vlantag,若存在
则不再添加vlantag,因为有了这个判断,所以就没有对vlan_dev->flag进行判断了。
其实vlan_dev_hard_start_xmit中仅仅判断是否带vlantag来决定是否添加vlantag,本身是有问题的,即没法添加双层vlantag了。其实我感觉此处可以修改一下,将判断修改如下:if(dev->flag&0x1!=0),这样就可以处理多层vlan,也可以处理在vlan_dev_hard_header中已添加vlan的case。
intvlan_dev_hard_header(structsk_buff*skb,structnet_device*dev,
unsignedshorttype,void*daddr,void*saddr,
unsignedlen)
{
structvlan_hdr*vhdr;
unsignedshortveth_TCI=0;
intrc=0;
intbuild_vlan_header=0;
structnet_device*vdev=dev;/*savethisforthebottomofthemethod*/
#ifdefVLAN_DEBUG
printk(VLAN_DBG"%s:skb:%ptype:%hxlen:%xvlan_id:%hx,daddr:%p\n",
__FUNCTION__,skb,type,len,VLAN_DEV_INFO(dev)->vlan_id,daddr);
#endif
/*buildvlanheaderonlyifre_order_headerflagisNOTset.This
*fixessomeprogramsthatgetconfusedwhentheyseeaVLANdevice
*sendingaframethatisVLANencoded(theconsensusisthattheVLAN
*deviceshouldlookcompletelylikeanEthernetdevicewhenthe
*REORDER_HEADERflagisset) Thedrawbacktothisissomeextra
*headershufflinginthehard_start_xmit.Userscanturnoffthis
*REORDERbehaviourwiththevconfigtool.
*/
build_vlan_header=((VLAN_DEV_INFO(dev)->flags&1)==0);
if(build_vlan_header){
vhdr=(structvlan_hdr*)skb_push(skb,VLAN_HLEN);
/*buildthefourbytesthatmakethisaVLANheader.*/
/*Now,constructthesecondtwobytes.Thisfieldlookssomething
*like:
*usr_priority:3bits (highbits)
*CFI 1bit
*VLANID 12bits(lowbits)
*
*/
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);
/*
*Settheprotocoltype.
*ForapacketoftypeETH_P_802_3weputthelengthinhereinstead.
*Itisuptothe802.2layertocarryprotocolinformation.
*/
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;
}
/*Beforedelegatingworktothelowerlayer,enterourMAC-address*/
if(saddr==NULL)
saddr=dev->dev_addr;
dev=VLAN_DEV_INFO(dev)->real_dev;
/*MPLScansendusskbuffsw/outenoughspace. Thischeckwillgrowthe
*skbifitdoesn’thaveenoughheadroom.Notabeautifulsolution,so
*I’lltickacountersothatuserscanknowit’shappening… Ifthey
*care…
*/
/*NOTE:Thismaystillbreakiftheunderlyingdeviceisnotthefinal
*device(andthustherearemoreheaderstoadd…)Itshouldworkfor
*good-ole-ethernetthough.
*/
if(skb_headroom(skb)<dev->hard_header_len){
structsk_buff*sk_tmp=skb;
skb=skb_realloc_headroom(sk_tmp,dev->hard_header_len);
kfree_skb(sk_tmp);
if(skb==NULL){
structnet_device_stats*stats=vlan_dev_get_stats(vdev);
stats->tx_dropped++;
return-ENOMEM;
}
VLAN_DEV_INFO(vdev)->cnt_inc_headroom_on_tx++;
#ifdefVLAN_DEBUG
printk(VLAN_DBG"%s:%s:hadtogrowskb.\n",__FUNCTION__,vdev->name);
#endif
}
if(build_vlan_header){
/*Nowmaketheunderlyingrealhardheader*/
rc=dev->hard_header(skb,dev,ETH_P_8021Q,daddr,saddr,len+VLAN_HLEN);
if(rc>0){
rc+=VLAN_HLEN;
}elseif(rc<0){
rc-=VLAN_HLEN;
}
}else{
/*Ifhere,thenwe’lljustmakeanormallookingethernetframe,
*but,thehard_start_xmitmethodwillinsertthetag(ithasto
*beabletodothisforbridgedandotherskbsthatdon’tcome
*downtheprotocolstackinanorderlymanner.
*/
rc=dev->hard_header(skb,dev,type,daddr,saddr,len);
}
returnrc;
}
2.1.4vlan_dev_hard_start_xmit
功能:vlandevice的hard_start_xmit函数(realdev对应的网口不支持硬件vlan的hard_start_xmit函数)
1.当数据包没有携带vlan时,则根据数据包的priority、vlandevice的vlanid,组建vlan值,
并添加到数据包中
2.增加统计计数,修改数据包的dev指向realdevice
3.调用dev_queue_xmit,让数据从realdevice对应的网卡发送出去。
通过该函数可以看出,linux仅对untag的数据进行添加vlan的操作,当数据包以及
包含vlan时,则不会再进行添加vlan的操作。因此若想要支持双层vlan,还是需要
修改linux的vlan架构的。
intvlan_dev_hard_start_xmit(structsk_buff*skb,structnet_device*dev)
{
structnet_device_stats*stats=vlan_dev_get_stats(dev);
structvlan_ethhdr*veth=(structvlan_ethhdr*)(skb->data);
/*Handlenon-VLANframesiftheyaresenttous,forexamplebyDHCP.
*
*NOTE:THISASSUMESDIXETHERNET,SPECIFICALLYNOTSUPPORTING
*OTHERTHINGSLIKEFDDI/TokenRing/802.3SNAPs…
*/
if(veth->h_vlan_proto!=__constant_htons(ETH_P_8021Q)){
intorig_headroom=skb_headroom(skb);
unsignedshortveth_TCI;
/*ThisisnotaVLANframe…butwecanfixthat!*/
VLAN_DEV_INFO(dev)->cnt_encap_on_xmit++;
#ifdefVLAN_DEBUG
printk(VLAN_DBG"%s:prototoencap:0x%hx(hbo)\n",
__FUNCTION__,htons(veth->h_vlan_proto));
#endif
/*Constructthesecondtwobytes.Thisfieldlookssomething
*like:
*usr_priority:3bits (highbits)
*CFI 1bit
*VLANID 12bits(lowbits)
*/
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++;
return0;
}
if(orig_headroom<VLAN_HLEN){
VLAN_DEV_INFO(dev)->cnt_inc_headroom_on_tx++;
}
}
#ifdefVLAN_DEBUG
printk(VLAN_DBG"%s:abouttosendskb:%ptodev:%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++;/*forstaticsonly*/
stats->tx_bytes+=skb->len;
skb->dev=VLAN_DEV_INFO(dev)->real_dev;
dev_queue_xmit(skb);
return0;
}
2.1.5vlan_dev_change_mtu
/*
功能:修改vlandevice的mtu值
*/
intvlan_dev_change_mtu(structnet_device*dev,intnew_mtu)
{
/*TODO:gottamakesuretheunderlyinglayercanhandleit,
*maybeanIFF_VLAN_CAPABLEflagfordevices?
*/
if(VLAN_DEV_INFO(dev)->real_dev->mtu<new_mtu)
return-ERANGE;
dev->mtu=new_mtu;
return0;
}
2.1.6register_vlan_device
该函数实现注册一个vlandevice设备,并实现对structnet_device的相应接口函数进行赋值,实现structnet_device的接口函数指向上述2.1.1-2.1.5中介绍的函数,通过该函数就真正的注册了一个vlandevice设备,其二层发送函数就完全是vlan的处理了。这个函数还是比较重要的。
staticstructnet_device*register_vlan_device(constchar*eth_IF_name,
unsignedshortVLAN_ID)
{
structvlan_group*grp;
structnet_device*new_dev;
structnet_device*real_dev;/*theethernetdevice*/
charname[IFNAMSIZ];
inti;
#ifdefVLAN_DEBUG
printk(VLAN_DBG"%s:if_name-:%s:- vid:%i\n",
__FUNCTION__,eth_IF_name,VLAN_ID);
#endif
if(VLAN_ID>=VLAN_VID_MASK)
gotoout_ret_null;
/*findthedevicerelatingtoeth_IF_name.*/
real_dev=dev_get_by_name(eth_IF_name);
if(!real_dev)
gotoout_ret_null;
if(real_dev->features&NETIF_F_VLAN_CHALLENGED){
printk(VLAN_DBG"%s:VLANsnotsupportedon%s.\n",
__FUNCTION__,real_dev->name);
gotoout_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%shasbuggyVLANhwaccel.\n",
__FUNCTION__,real_dev->name);
gotoout_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%shasbuggyVLANhwaccel.\n",
__FUNCTION__,real_dev->name);
gotoout_put_dev;
}
/*Fromthispointon,allthedatastructuresmustremain
*consistent.
*/
rtnl_lock();
/*Therealdevicemustbeupandoperatinginorderto
*assosciateaVLANdevicewithit.
*/
if(!(real_dev->flags&IFF_UP))
gotoout_unlock;
/*
若在该realdevice中,已经创建了该vid相关的vlandevice,则返回
失败
*/
if(__find_vlan_dev(real_dev,VLAN_ID)!=NULL){
/*wasalreadyregistered.*/
printk(VLAN_DBG"%s:ALREADYhadVLANregistered\n",__FUNCTION__);
gotoout_unlock;
}
/*Gottasetupthefieldsforthedevice.*/
#ifdefVLAN_DEBUG
printk(VLAN_DBG"Abouttoallocatename,vlan_name_type:%i\n",
vlan_name_type);
#endif
/*
根据vlanname的生成类型,生成相应的vlandevice的名称
*/
switch(vlan_name_type){
caseVLAN_NAME_TYPE_RAW_PLUS_VID:
/*namewilllooklike: eth1.0005*/
snprintf(name,IFNAMSIZ,"%s.%.4i",real_dev->name,VLAN_ID);
break;
caseVLAN_NAME_TYPE_PLUS_VID_NO_PAD:
/*Putourvlan.VIDinthename.
*Namewilllooklike: vlan5
*/
snprintf(name,IFNAMSIZ,"vlan%i",VLAN_ID);
break;
caseVLAN_NAME_TYPE_RAW_PLUS_VID_NO_PAD:
/*Putourvlan.VIDinthename.
*Namewilllooklike: eth0.5
*/
snprintf(name,IFNAMSIZ,"%s.%i",real_dev->name,VLAN_ID);
break;
caseVLAN_NAME_TYPE_PLUS_VID:
/*Putourvlan.VIDinthename.
*Namewilllooklike: vlan0005
*/
default:
snprintf(name,IFNAMSIZ,"vlan%.4i",VLAN_ID);
};
/*
创建网络接口设备,并传递函数vlan_setup设置vlandevice关联的接口函数指针
vlan_dev_change_mtu、vlan_dev_open、vlan_dev_stop、vlan_dev_set_mac_address、vlan_dev_ioctl等
*/
new_dev=alloc_netdev(sizeof(structvlan_dev_info),name,
vlan_setup);
if(new_dev==NULL)
gotoout_unlock;
#ifdefVLAN_DEBUG
printk(VLAN_DBG"Allocatednewname-:%s:-\n",new_dev->name);
#endif
/*IFF_BROADCAST|IFF_MULTICAST;???*/
/*初始化vlandevice的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);
/*need4bytesforextraVLANheaderinfo,
*hopetheunderlyingdevicecanhandleit.
*/
new_dev->mtu=real_dev->mtu;
/*TODO:maybejustassignittobeETHERNET?*/
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)){
/*Regularethernet+4bytes(18total).*/
new_dev->hard_header_len+=VLAN_HLEN;
}
VLAN_MEM_DBG("new_dev->privmalloc,addr:%psize:%i\n",
new_dev->priv,
sizeof(structvlan_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;
/*
根据realdevice是否支持NETIF_F_HW_VLAN_TX,让vlandevice的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;
/*设置该vlandevice关联的vlanid、realdevice、flag*/
VLAN_DEV_INFO(new_dev)->vlan_id=VLAN_ID;/*1throughVLAN_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;
#ifdefVLAN_DEBUG
printk(VLAN_DBG"Abouttogofindthegroupforidx:%i\n",
real_dev->ifindex);
#endif
if(register_netdevice(new_dev))
gotoout_free_newdev;
lockdep_set_class(&new_dev->_xmit_lock,&vlan_netdev_xmit_lock_key);
/*设置vlandevice关联的realdevice的index*/
new_dev->iflink=real_dev->ifindex;
vlan_transfer_operstate(real_dev,new_dev);
linkwatch_fire_event(new_dev);/*_MUST_callrfc2863_policy()*/
/*So,gotthesuckerinitialized,nowletsplace
*itintoourlocalstructure.
*/
/*
判断该realdevice是否已经存在vlangroup,若存在,则将vlandevice存入grp->vlan_devices_arrays
中相应的数组中。
这个vlan_group变量实现了查找一个realdevice所关联的所有vlandevice的操作。
由于vlandevice、realdevice都是使用的structnet_device变量,因此目前代码中,没有将
vlan_group定义在net_device中,而是重新定义了一个全局的hash链表数组中。
那么问题来了,可不可以在structnet_device中增加一个链表头指针,将该realdevice
关联的vlandevice设备都添加到该链表中呢?
当然是可以的,这样做的话,在查找一个realdevice关联的所有vlandevice时,其查找
时间肯定比当前的查找时间要快的(因为不用进行hash相关的操作),但是每一个
structnet_device变量都增加了一个链表指针,增加了对内存的使用,这个就需要在
内存与性能之间做取舍了。
*/
grp=__vlan_find_group(real_dev->ifindex);
/*Note,wearerunningundertheRTNLsemaphore
*soitcannot"appear"onus.
*/
if(!grp){/*needtoaddanewgroup*/
grp=kzalloc(sizeof(structvlan_group),GFP_KERNEL);
if(!grp)
gotoout_free_unregister;
for(i=0;i<VLAN_GROUP_ARRAY_SPLIT_PARTS;i++){
grp->vlan_devices_arrays[i]=kzalloc(
sizeof(structnet_device*)*VLAN_GROUP_ARRAY_PART_LEN,
GFP_KERNEL);
if(!grp->vlan_devices_arrays[i])
gotoout_free_arrays;
}
/*printk(KERN_ALERT"VLANREGISTER:Allocatednewgroup.\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)/*createit’sprocentry*/
printk(KERN_WARNING"VLAN:failedtoaddprocentryfor%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();
#ifdefVLAN_DEBUG
printk(VLAN_DBG"Allocatednewdevicesuccessfully,returning.\n");
#endif
returnnew_dev;
out_free_arrays:
vlan_group_free(grp);
out_free_unregister:
unregister_netdev(new_dev);
gotoout_unlock;
out_free_newdev:
free_netdev(new_dev);
out_unlock:
rtnl_unlock();
out_put_dev:
dev_put(real_dev);
out_ret_null:
returnNULL;
}
通过以上几个函数,就实现了一个vlan类型的structnet_device变量,创建了一个新的虚拟的网络接口。
2.2Linuxvlan模块与socket模块的关联
本小节分析下vlan模块在socket模块中注册的ioctl,以实现vlan接口的创建与删除等。
2.2.1vlan_ioctl_set
该函数主要是为函数指针vlan_ioctl_hook赋值,而vlan_ioctl_hook则会socket的ioctl相关的命令下面,用于处理vlan相关的命令
voidvlan_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.2vlan_ioctl_handler
功能:vlan模块相关的ioctl接口函数
staticintvlan_ioctl_handler(void__user*arg)
{
interr=0;
unsignedshortvid=0;
structvlan_ioctl_argsargs;
if(copy_from_user(&args,arg,sizeof(structvlan_ioctl_args)))
return-EFAULT;
/*Nullterminatethissucker,justincase.*/
args.device1[23]=0;
args.u.device2[23]=0;
#ifdefVLAN_DEBUG
printk(VLAN_DBG"%s:args.cmd:%x\n",__FUNCTION__,args.cmd);
#endif
switch(args.cmd){
caseSET_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;
caseSET_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;
caseSET_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;
caseSET_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;
caseADD_VLAN_CMD:
if(!capable(CAP_NET_ADMIN))
return-EPERM;
/*wehavebeengiventhenameoftheEthernetDevicewewantto
*talkto:args.dev1 Wealsohavethe
*VLANID:args.u.VID
*/
if(register_vlan_device(args.device1,args.u.VID)){
err=0;
}else{
err=-EINVAL;
}
break;
caseDEL_VLAN_CMD:
if(!capable(CAP_NET_ADMIN))
return-EPERM;
/*Here,theargs.dev1istheactualVLANwewant
*togetridof.
*/
err=unregister_vlan_device(args.device1);
break;
caseGET_VLAN_INGRESS_PRIORITY_CMD:
/*TODO:Implement
err=vlan_dev_get_ingress_priority(args);
if(copy_to_user((void*)arg,&args,
sizeof(structvlan_ioctl_args))){
err=-EFAULT;
}
*/
err=-EINVAL;
break;
caseGET_VLAN_EGRESS_PRIORITY_CMD:
/*TODO:Implement
err=vlan_dev_get_egress_priority(args.device1,&(args.args);
if(copy_to_user((void*)arg,&args,
sizeof(structvlan_ioctl_args))){
err=-EFAULT;
}
*/
err=-EINVAL;
break;
caseGET_VLAN_REALDEV_NAME_CMD:
err=vlan_dev_get_realdev_name(args.device1,args.u.device2);
if(err)
gotoout;
if(copy_to_user(arg,&args,
sizeof(structvlan_ioctl_args))){
err=-EFAULT;
}
break;
caseGET_VLAN_VID_CMD:
err=vlan_dev_get_vid(args.device1,&vid);
if(err)
gotoout;
args.u.VID=vid;
if(copy_to_user(arg,&args,
sizeof(structvlan_ioctl_args))){
err=-EFAULT;
}
break;
default:
/*passontounderlyingdeviceinstead??*/
printk(VLAN_DBG"%s:UnknownVLANCMD:%x\n",
__FUNCTION__,args.cmd);
return-EINVAL;
};
out:
returnerr;
}
这个函数里,有几个函数比较重要,我们需要好好分析下,这几个函数为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
/*Flagsaredefinedinthevlan_dev_infoclassininclude/linux/if_vlan.hfile.*/
/*
功能:设置vlandevice的flag值,即是否支持reordervlanheader。
*/
intvlan_dev_set_vlan_flag(char*dev_name,__u32flag,shortflag_val)
{
structnet_device*dev=dev_get_by_name(dev_name);
if(dev){
if(dev->priv_flags&IFF_802_1Q_VLAN){
/*verifyflagissupported*/
if(flag==1){
if(flag_val){
VLAN_DEV_INFO(dev)->flags|=1;
}else{
VLAN_DEV_INFO(dev)->flags&=~1;
}
dev_put(dev);
return0;
}else{
printk(KERN_ERR"%s:flag%iisnotvalid.\n",
__FUNCTION__,(int)(flag));
dev_put(dev);
return-EINVAL;
}
}else{
printk(KERN_ERR
"%s:%sisnotavlandevice,priv_flags:%hX.\n",
__FUNCTION__,dev->name,dev->priv_flags);
dev_put(dev);
}
}else{
printk(KERN_ERR"%s:Couldnotfinddevice:%s\n",
__FUNCTION__,dev_name);
}
return-EINVAL;
}
2.2.2.2vlan_dev_set_ingress_priority
/*
设置vlan优先级与数据包优先级的map
*/
intvlan_dev_set_ingress_priority(char*dev_name,__u32skb_prio,shortvlan_prio)
{
structnet_device*dev=dev_get_by_name(dev_name);
if(dev){
if(dev->priv_flags&IFF_802_1Q_VLAN){
/*seeifaprioritymappingexists..*/
VLAN_DEV_INFO(dev)->ingress_priority_map[vlan_prio&0x7]=skb_prio;
dev_put(dev);
return0;
}
dev_put(dev);
}
return-EINVAL;
}
2.2.2.3vlan_dev_set_egress_priority
/*
功能:设置输出数据包的qos与优先级之间的关联
*/
intvlan_dev_set_egress_priority(char*dev_name,__u32skb_prio,shortvlan_prio)
{
structnet_device*dev=dev_get_by_name(dev_name);
structvlan_priority_tci_mapping*mp=NULL;
structvlan_priority_tci_mapping*np;
if(dev){
if(dev->priv_flags&IFF_802_1Q_VLAN){
/*Seeifaprioritymappingexists..*/
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);
return0;
}
mp=mp->next;
}
/*Createanewmappingthen.*/
mp=VLAN_DEV_INFO(dev)->egress_priority_map[skb_prio&0xF];
np=kmalloc(sizeof(structvlan_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);
return0;
}else{
dev_put(dev);
return-ENOBUFS;
}
}
dev_put(dev);
}
return-EINVAL;
}
2.2.2.4register_vlan_device
这个函数在2.1.6中以及介绍过了,此处就不分析了。
此处就分析一下该函数调用的几个函数吧。
功能:根据real_dev、vid,查找依附于real_dev上的vlandevice
structnet_device*__find_vlan_dev(structnet_device*real_dev,
unsignedshortVID)
{
structvlan_group*grp=__vlan_find_group(real_dev->ifindex);
if(grp)
returnvlan_group_get_device(grp,VID);
returnNULL;
}
/*
功能:设置vlandevice关联的接口函数指针
*/
staticvoidvlan_setup(structnet_device*new_dev)
{
SET_MODULE_OWNER(new_dev);
/*new_dev->ifindex=0;itwillbesetwhenaddedto
*thegloballist.
*iflinkissetaswell.
*/
new_dev->get_stats=vlan_dev_get_stats;
/*MakethisthingknownasaVLANdevice*/
new_dev->priv_flags|=IFF_802_1Q_VLAN;
/*Setusuptohavenoqueue,astheunderlyingHardwaredevice
*candoallthequeueingwecouldwant.
*/
new_dev->tx_queue_len=0;
/*setupmethodcalls*/
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;
}
/*
功能:根据realdevice的index,查找与该realdevice关联的vlangroup变量
*/
staticstructvlan_group*__vlan_find_group(intreal_dev_ifindex)
{
structvlan_group*grp;
structhlist_node*n;
inthash=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)
returngrp;
}
returnNULL;
}
/*
功能:根据vlanid以及vlangroup变量,在vlan_devices_arrays中增加相应的vlan设备
由于是直接增加设备,没有进行存在性判断,因此在调用该函数
之前,应该调用vlan_group_get_device判断要添加的vlan设备是否已存在。
*/
staticinlinevoidvlan_group_set_device(structvlan_group*vg,intvlan_id,
structnet_device*dev)
{
structnet_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.5vlan_dev_get_realdev_name
功能:根据vlandevice的名称,获取其所依附的realdevice的名称。
intvlan_dev_get_realdev_name(constchar*dev_name,char*result)
{
structnet_device*dev=dev_get_by_name(dev_name);
intrv=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;
}
returnrv;
}
2.2.2.6vlan_dev_get_vid
功能:查找vlan设备关联的vlanid
intvlan_dev_get_vid(constchar*dev_name,unsignedshort*result)
{
structnet_device*dev=dev_get_by_name(dev_name);
intrv=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;
}
returnrv;
}
至此将linuxvlan模块与socket的关系分析完了。
2.3Linuxvlan模块与协议栈处理函数的关联
既然新开发一个vlan的功能模块,自然需要融入当前的协议栈代码空间中。对于数据流来说也就tx、rx两个方向罢了,tx方向就是vlandevice的hard_start_xmit函数,只要在注册structnet_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的实现方式。
在linx2.6的内核里,是通过将dev_add_pack将该接收函数注册到三层协议相关的接收函数的链表里的。即把vlan的接收函数与ip、ipv6等协议的接收函数注册到同一个链表里的。
但是考虑到vlan毕竟是属于二层协议的范畴,因此在linux3.x中,对剥除vlantag的操作进行了调整,即在netif_receive_skb中,即调用vlan_untag操作,剥除数据包的vlantag,接着调用vlan_do_receive修改skb->dev的值,接着重新返回到vlan_untag的起始调用处,即实现了从real_dev->vlan_dev的转换。这样既将vlan的剥除与三层协议相关的接收函数区别开来,又省去了netif_rx的调用。
对于linux2.6.x与linux3.xvlan模块的实现差别,再多说一些;
对于linux3.x以上版本的vlan功能模块,在数据包从vlandev发送出去时,仅仅设置skb->vlan_tci的值,而不在数据包中增加vlan头部信息。增加vlan头部的操作,是在将skb->dev设置为realdev后,在dev_hard_start_xmit中根据skb->vlan_tci的值来决定是否添加vlan头部。而linux3.x以下的版本,则是在vlandev的dev->netdev_ops->ndo_start_xmit进行vlantag的添加,然后才修改skb->dev的值为realdev。
在linux3.x以下,接收方向是在将skb->dev的值修改为vlandev后,才剥除vlantag的;发送方向,则是在添加vlantag后,才修改skb->dev的值为realdev。
在linux3.x以上,接收方向的数据包,只要有vlantag,则会把vlantag剥除掉,之后再将skb->dev修改成vlandev;发送方向,则是在将skb->dev的值修改为realdev后,再添加vlantag。
一个是在vlandev处进行vlantag的剥除与添加,一个策略是在realdev处进行vlantag的剥除与添加。
本文是基于linux2.6.21内核进行分析的,因此还是把vlan作为三层协议注册接收函数的方式。下面分析一下函数vlan_skb_recv。
2.3.1vlan_skb_recv
vlan设备的接收处理函数。
功能:将数据包携带的vlan头部剥除掉
intvlan_skb_recv(structsk_buff*skb,structnet_device*dev,
structpacket_type*ptype,structnet_device*orig_dev)
{
unsignedchar*rawp=NULL;
structvlan_hdr*vhdr=(structvlan_hdr*)(skb->data);
unsignedshortvid;
structnet_device_stats*stats;
unsignedshortvlan_TCI;
__be16proto;
/*vlan_TCI=ntohs(get_unaligned(&vhdr->h_vlan_TCI));*/
vlan_TCI=ntohs(vhdr->h_vlan_TCI);
vid=(vlan_TCI&VLAN_VID_MASK);
#ifdefVLAN_DEBUG
printk(VLAN_DBG"%s:skb:%pvlan_id:%hx\n",
__FUNCTION__,skb,vid);
#endif
/*Ok,wewillfindthecorrectVLANdevice,striptheheader,
*andthengoonasusual.
*/
/*Wehave12bitsofvlanID.
*
*Wemustnotdropallowpreemptuntilweholda
*referencetothedevice(netif_rxdoesthat)orwe
*fail.
*/
rcu_read_lock();
/*修改数据包的dev指针,再次调用netif_rx函数,以实现
数据包从real_dev->vlan_dev的流程,以实现逻辑意义上的
数据包在两个网口间的传递*/
skb->dev=__find_vlan_dev(dev,vid);
if(!skb->dev){
rcu_read_unlock();
#ifdefVLAN_DEBUG
printk(VLAN_DBG"%s:ERROR:Nonet_deviceforVID:%iondev:%s[%i]\n",
__FUNCTION__,(unsignedint)(vid),dev->name,dev->ifindex);
#endif
kfree_skb(skb);
return-1;
}
skb->dev->last_rx=jiffies;
/*BumptherxcountersfortheVLANdevice.*/
/*
增加vlan设备的rx统计计数
*/
stats=vlan_dev_get_stats(skb->dev);
stats->rx_packets++;
stats->rx_bytes+=skb->len;
/*TakeofftheVLANheader(4bytescurrently)*/
/*skb->data指针后移4个字节*/
skb_pull_rcsum(skb,VLAN_HLEN);
/*Ok,letschecktomakesurethedevice(dev)we
*cameinoniswhatthisVLANisattachedto.
*/
/*若接收数据包的网络设备与vlan设备所依附的真实设备
不匹配,则释放该数据包占用的内存,并返回错误*/
if(dev!=VLAN_DEV_INFO(skb->dev)->real_dev){
rcu_read_unlock();
#ifdefVLAN_DEBUG
printk(VLAN_DBG"%s:droppingskb:%pbecausecameinonwrongdevice,dev:%sreal_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;
}
/*
*Dealwithingressprioritymapping.
*/
skb->priority=vlan_get_ingress_priority(skb->dev,ntohs(vhdr->h_vlan_TCI));
#ifdefVLAN_DEBUG
printk(VLAN_DBG"%s:priority:%luforTCI:%hu(hbo)\n",
__FUNCTION__,(unsignedlong)(skb->priority),
ntohs(vhdr->h_vlan_TCI));
#endif
/*Theethernetdriveralreadydidthepkt_typecalculations
*forus…
*/
switch(skb->pkt_type){
casePACKET_BROADCAST:/*Yeah,statscollectthesetogether..*/
//stats->broadcast++;//nosuchcounter:-(
break;
casePACKET_MULTICAST:
stats->multicast++;
break;
casePACKET_OTHERHOST:
/*Ourlowerlayerthinksthisisnotlocal,let’smakesure.
*ThisallowstheVLANtohaveadifferentMACthantheunderlying
*device,andstillroutecorrectly.
*/
if(!compare_ether_addr(eth_hdr(skb)->h_dest,skb->dev->dev_addr)){
/*Itisforour(changed)MAC-address!*/
skb->pkt_type=PACKET_HOST;
}
break;
default:
break;
};
/*WasaVLANpacket,grabtheencapsulatedprotocol,whichthelayer
*threeprotocolscareabout.
*/
/*proto=get_unaligned(&vhdr->h_vlan_encapsulated_proto);*/
proto=vhdr->h_vlan_encapsulated_proto;
skb->protocol=proto;
if(ntohs(proto)>=1536){
/*placeitbackonthequeuetobehandledby
*truelayer3protocols.
*/
/*Seeifweareconfiguredtore-writetheVLANheader
*tomakeitlooklikeethernet…
*/
skb=vlan_check_reorder_header(skb);
/*Canbenullifskb-clonefailswhenre-ordering*/
if(skb){
netif_rx(skb);
}else{
/*TODO:Addamorespecificcounterhere.*/
stats->rx_errors++;
}
rcu_read_unlock();
return0;
}
rawp=skb->data;
/*
*ThisisamagichacktospotIPXpackets.OlderNovellbreaks
*theprotocoldesignandrunsIPXover802.3withoutan802.2LLC
*layer.WelookforFFFFwhichisn’taused802.2SSAP/DSAP.This
*won’tworkforfaulttolerantnetwarebutdoesfortherest.
*/
if(*(unsignedshort*)rawp==0xFFFF){
skb->protocol=__constant_htons(ETH_P_802_3);
/*placeitbackonthequeuetobehandledbytruelayer3protocols.
*/
/*Seeifweareconfiguredtore-writetheVLANheader
*tomakeitlooklikeethernet…
*/
skb=vlan_check_reorder_header(skb);
/*Canbenullifskb-clonefailswhenre-ordering*/
if(skb){
netif_rx(skb);
}else{
/*TODO:Addamorespecificcounterhere.*/
stats->rx_errors++;
}
rcu_read_unlock();
return0;
}
/*
* Real802.2LLC
*/
skb->protocol=__constant_htons(ETH_P_802_2);
/*placeitbackonthequeuetobehandledbyupperlayerprotocols.
*/
/*Seeifweareconfiguredtore-writetheVLANheader
*tomakeitlooklikeethernet…
*/
skb=vlan_check_reorder_header(skb);
/*Canbenullifskb-clonefailswhenre-ordering*/
if(skb){
netif_rx(skb);
}else{
/*TODO:Addamorespecificcounterhere.*/
stats->rx_errors++;
}
rcu_read_unlock();
return0;
}
而该函数的注册,则是通过以下代码段实现的:
三层协议接收函数注册相关的结构体,此处为注册vlan的接收处理函数的回调函数
staticstructpacket_typevlan_packet_type={
.type=__constant_htons(ETH_P_8021Q),
.func=vlan_skb_recv,/*VLANreceivemethod*/
};
dev_add_pack(&vlan_packet_type);
关于dev_add_pack的分析,可以看下先前的一个分析文档:linux3、4层接收函数的注册分析
至此将几个主要的部分都分析完了,接着就分析仪器vlan模块的初始化函数vlan_proto_init
三、vlan模块的初始化
3.1vlan_proto_init
vlan协议相关初始化
1.在proc文件系统下注册vlan相关的文件目录
2.在通知链netdev_chain注册vlan相关的回调处理函数
3.在三层协议接收处理函数相关的注册链表中添加vlan的注册
以实现对接收到的vlan数据包的处理。
4.调用vlan_ioctl_set将vlan模块相关的ioctl接口函数注册到socket中,在socket中增加
相应的ioctl命令,实现vlan模块与socket模块的关联。
staticint__initvlan_proto_init(void)
{
interr;
printk(VLAN_INF"%sv%s%s\n",
vlan_fullname,vlan_version,vlan_copyright);
printk(VLAN_INF"Allbugsaddedby%s\n",
vlan_buggyright);
/*procfilesysteminitialization*/
err=vlan_proc_init();
if(err<0){
printk(KERN_ERR
"%s%s:can’tcreateentryinprocfilesystem!\n",
__FUNCTION__,VLAN_NAME);
returnerr;
}
dev_add_pack(&vlan_packet_type);
/*Registerustoreceivenetdeviceevents*/
err=register_netdevice_notifier(&vlan_notifier_block);
if(err<0){
dev_remove_pack(&vlan_packet_type);
vlan_proc_cleanup();
returnerr;
}
vlan_ioctl_set(vlan_ioctl_handler);
return0;
}
至此,就算是把vlan模块分析完了,可能还有些地方分析的不准确,后续如果有更深入的内容,会进行补充的。
最大的成功在于最大的付出。