Linux vlan 功能模块分析

本文代码基于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模块分析完了,可能还有些地方分析的不准确,后续如果有更深入的内容,会进行补充的。

最大的成功在于最大的付出。

Linux vlan 功能模块分析

相关文章:

你感兴趣的文章:

标签云: