Linux vlan 功能模块分析

时间:2023-02-05 22:13:08

本文代码基于linux2.6.21

Vlan即虚拟局域网,一个vlan能够模拟一个常规的交换网络,实现了将一个物理的交换机划分成多个逻辑的交换网络。而不同的vlan之间如果要进行通信就要通过三层协议来实现。

 

linuxvlan的配置使用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 deviceindex*/

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_hashhash链表链接在一起的,其定义如下:

static struct hlist_head vlan_group_hash[VLAN_GRP_HASH_SIZE];

 

由于vlan devicereal device都是使用的struct net_device变量,因此目前代码中,没有将vlan_group定义在net_device中,而是重新定义了一个全局的hash链表数组中。

那么问题来了,可不可以在struct net_device中增加一个链表头指针,将该real device

关联的vlan device设备都添加到该链表中呢?

当然是可以的,这样做的话,在查找一个real device关联的所有vlan device时,其查找

时间肯定比当前的查找时间要快的(因为不用进行hash相关的操作),但是每一个struct net_device变量都增加了一个链表指针,增加了对内存的使用,这个就需要在内存与性能之间做取舍了。

 

 

相应的setget的接口函数如下:

/*

功能:根据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_devicepriv指针变量指向的内存空间中。

 

#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_headervlan_dev_hard_start_xmitvlan_dev_openvlan_dev_stop

2. Linux vlan模块与socket模块的关联,因为linux vlan 模块与应用层是密切相关的,而对于协议栈来说,应用层与kernel层的通信一般使用socketioctl来实现,因此就需要在linux vlan 模块实现socket ioctl相关的接口函数,以实现vlan device的添加、删除、优先级的映射等,并在linux socketioctl中注册相应的接口函数

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 headervlan device而言,此处仅需要增加增加二层头部即可,

  不需要增加vlan tagvlan tag是在vlan_dev_hard_start_xmit中添加

2.对于不支持reorder headervlan 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中已添加vlancase

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 devicehard_start_xmit函数(real dev对应的网口不支持硬件vlanhard_start_xmit函数)

 

1.当数据包没有携带vlan时,则根据数据包的priorityvlan devicevlanid,组建vlan值,

  并添加到数据包中

2.增加统计计数,修改数据包的dev指向real device

3.调用dev_queue_xmit,让数据从real device对应的网卡发送出去。

 

通过该函数可以看出,linux仅对untag的数据进行添加vlan 的操作,当数据包以及

包含vlan时,则不会再进行添加vlan的操作。因此若想要支持双层vlan,还是需要

修改linuxvlan架构的。

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 devicemtu

*/

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_mtuvlan_dev_openvlan_dev_stopvlan_dev_set_mac_addressvlan_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 deviceflagsstatemtutypehard_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 devicehard_headerhard_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 idreal deviceflag*/

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 devicereal 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则会socketioctl相关的命令下面,用于处理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函数里,针对命令SIOCGIFVLANSIOCSIFVLAN,则会调用vlan_ioctl_hook进行相应的处理。

而在vlan_proto_init函数里,通过vlan_ioctl_set(vlan_ioctl_handler),将vlan_ioctl_hook指向了函数vlan_ioctl_handler,这样对于socketioctlSIOCGIFVLANSIOCSIFVLAN命令,即会调用函数数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_flagregister_vlan_deviceregister_vlan_device

vlan_dev_get_realdev_namevlan_dev_get_vidvlan_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 deviceflag值,即是否支持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_devvid,查找依附于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的功能模块,自然需要融入当前的协议栈代码空间中。对于数据流来说也就txrx两个方向罢了,tx方向就是vlan devicehard_start_xmit函数,只要在注册struct net_device变量时,对其hard_start_xmit指针进行赋值即可,这个赋值操作在register_vlan_device中已经实现了,且vlan_dev_hard_start_xmit函数也在2.1.4中分析过了。

那还需要考虑接收方向的vlan处理函数了。

 

那对于vlan的处理放在哪里比较合适呢?

如果按协议栈来说的话,那因为vlan头部在二层mac头部后面,如果把其作为一个三层协议对待,像ipv4ipv6那样,在三层协议相关的回调函数链表中,增加vlan接收处理函数的链表成员即可。

但是,这样做虽然可以正常的剥掉vlan头部,但是vlan协议并不是严格意义上的三层协议啊,如果按这样考虑的话,则把vlan接收处理函数放在netif_receive_skb中进行调用反而会好一点。

那该选用哪一个方式呢?其实上面两种方式也就是linux2.6.xlinux3.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 devdev->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_setvlan模块相关的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模块分析完了,可能还有些地方分析的不准确,后续如果有更深入的内容,会进行补充的。