linux_VLAN

<strong>一、VLAN模块初始化</strong>
vlan_proto_init
//在/proc/net/目录下创建vlan目录,之后在vlan目录下创建一个config条目
//读取/proc/net/vlan/config可以显示所有vlan类型虚拟接口的vlan_id、对应的真实接口
vlan_proc_init()
proc_vlan_dir = proc_mkdir(name_root, proc_net);

proc_vlan_conf = create_proc_entry(name_conf,S_IFREG|S_IRUSR|S_IWUSR,
proc_vlan_dir);

proc_vlan_conf->proc_fops = &vlan_fops;

//向ptype_base中添加vlan协议类型的包支持,后续收到报文后会检查如果二层上面的
//负载类型为VLAN,则调用vlan_skb_recv进行后续收包处理。
//vlan_packet_type.type = __constant_htons(ETH_P_8021Q)
//vlan_packet_type.func = vlan_skb_recv
dev_add_pack(&vlan_packet_type);
hash = ntohs(pt->type) & 15;
list_add_rcu(&pt->list, &ptype_base[hash]);

//向接口设备模块注册通知链,当网络接口设备有变动时(如UP或DOWN等)触
//发vlan模块对应的处理。
register_netdevice_notifier(&vlan_notifier_block);

//添加vlan_ioctl_hook回调函数,当用户层使用套接口的ioctl进行SIOCGIFVLAN、
//SIOCSIFVLAN时,会使用该回调函数进行vlan相关的ioctl处理。
vlan_ioctl_set(vlan_ioctl_handler);
vlan_ioctl_hook = hook;

<strong>二、添加VLAN接口</strong>
1、用户侧命令 vconfig add eth0 100

vconfig命令代码流程:
vconfig_main
    //从cmds命令数组中查找匹配add字符的命令,当前p值为ADD_VLAN_CMD
    p = xfind_str(cmds+2, *argv);

    ifr.cmd = *p;

    //ifr.device1 = eth0
    strncpy_IFNAMSIZ(ifr.device1, argv[1]);

    p = argv[2];

if (ifr.cmd == ADD_VLAN_CMD)   
    //ifr.u.VID = 100
        ifr.u.VID = xatoul_range(p, 0, VLAN_GROUP_ARRAY_LEN-1);

    //创建流套接口
    fd = xsocket(AF_INET, SOCK_STREAM, 0);

    //在该套接口上执行ioctl操作,ioctl对应的命令为 SIOCSIFVLAN
    ioctl_or_perror_and_die(fd, SIOCSIFVLAN, &ifr,"ioctl error for %s", *argv);

2、内核侧接口
用户层的ioctl操作触发套接口对应的命令处理,当前不详细分析整个流程,其中vlan相关的ioctl命令处理回调是在vlan_proto_init中通过vlan_ioctl_set命令注入的。当前回调函数为vlan_ioctl_handler。

vlan_ioctl_handler
copy_from_user(&args, arg, sizeof(struct vlan_ioctl_args))

switch (args.cmd)
case ADD_VLAN_CMD:
//注册VLAN设备
//device1 = eth0
//vid = 100
register_vlan_device(args.device1, args.u.VID)
//根据设备名查找真实设备对象
real_dev = dev_get_by_name(eth_IF_name);

//如果当前设备标识不能处理VLAN包,则处理失败。
if (real_dev->features & NETIF_F_VLAN_CHALLENGED)
goto out_put_dev;

//如果当前硬件标记得支持VLAN接收,但对应回调为NULL,则处理失败。
if ((real_dev->features & NETIF_F_HW_VLAN_RX) &&
(real_dev->vlan_rx_register == NULL ||real_dev->vlan_rx_kill_vid == NULL))
goto out_put_dev;

//如果当前硬件标记支持VLAN过滤,但对应回调为NULL,则处理失败。
if ((real_dev->features & NETIF_F_HW_VLAN_FILTER) &&
(real_dev->vlan_rx_add_vid == NULL ||real_dev->vlan_rx_kill_vid == NULL))
goto out_put_dev;

//这里使用默认名称构造类型
switch (vlan_name_type)
case VLAN_NAME_TYPE_RAW_PLUS_VID_NO_PAD:
//name = eth0.100
snprintf(name, IFNAMSIZ, "%s.%i", real_dev->name, VLAN_ID);
break;

//设备对象构造
new_dev = alloc_netdev(sizeof(struct vlan_dev_info), name,vlan_setup);
alloc_size = (sizeof(*dev) + NETDEV_ALIGN_CONST) & 
~NETDEV_ALIGN_CONST;
alloc_size += sizeof_priv + NETDEV_ALIGN_CONST;

p = kzalloc(alloc_size, GFP_KERNEL);
dev = (struct net_device *)(((long)p + NETDEV_ALIGN_CONST) & 
~NETDEV_ALIGN_CONST);
dev->padded = (char *)dev - (char *)p;

if (sizeof_priv)
dev->priv = netdev_priv(dev);

//调用传入的setup函数指针,当前为vlan_setup,进行vlan相关私有
//参数设置。
setup(dev);
vlan_setup
new_dev->get_stats = vlan_dev_get_stats;

//标识这是一个VLAN虚拟接口
new_dev->priv_flags |= IFF_802_1Q_VLAN;

//发送队列长度为0,这样后续发包将不走队列流程。
new_dev->tx_queue_len = 0;

//设置一些回调函数。
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;

//eth0.100
strcpy(dev->name, name);

//从真实接口中获取标志,同时去除已经UP的标志。
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);

//从真实设备对象中复制MTU、及设备类型
new_dev->mtu = real_dev->mtu;
new_dev->type = real_dev->type;

//设置二层头长度,如果当前真实设备不支持VLAN发送,则由软交换负责
//VLAN发送处理,所以二层头需要新增VLAN头的长度。
new_dev->hard_header_len = real_dev->hard_header_len;
if (!(real_dev->features & NETIF_F_HW_VLAN_TX))
new_dev->hard_header_len += VLAN_HLEN;


//从真实设备中复制广播地址、物理地址
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;

//根据硬件设备是否支持VLAN发送来设置相关回调
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关键信息存储到设备对象私有成员中
//#define VLAN_DEV_INFO(x) ((struct vlan_dev_info *)(x->priv))
VLAN_DEV_INFO(new_dev)->vlan_id = VLAN_ID;		//100
VLAN_DEV_INFO(new_dev)->real_dev = real_dev;		//eth0
VLAN_DEV_INFO(new_dev)->dent = NULL;
//这个标记比较重要,主要在VLAN虚拟设备的行为,在收发包时是否
//进行去除/添加Vlan头的处理。
VLAN_DEV_INFO(new_dev)->flags = 1;

//给新建的接口分配接口索引,并添加到设备链中
register_netdevice(new_dev)

lockdep_set_class(&new_dev->_xmit_lock, &vlan_netdev_xmit_lock_key);

//关联真实设备索引
new_dev->iflink = real_dev->ifindex;

//新建的虚拟设备操作状态保持同真实设备相同,这里关注的是是否载波、
//是否休眠。
vlan_transfer_operstate(real_dev, new_dev);
if (dev->operstate == IF_OPER_DORMANT)
netif_dormant_on(vlandev);
else
netif_dormant_off(vlandev);

if (netif_carrier_ok(dev))
if (!netif_carrier_ok(vlandev))
netif_carrier_on(vlandev);
else
if (netif_carrier_ok(vlandev))
netif_carrier_off(vlandev);

//触发链路监控事件,当前假设关联的物理接口为up,并且载波已经存在。
//dev->operstate = IF_OPER_UP
linkwatch_fire_event(new_dev);
//触发链路监控事件,当前假设关联的物理接口为up,并且载波已经存在。
 
//使用关联的真实物理接口索引为hash句柄,在vlan_group_hash的hash链
//表中查找该物理接口是否已经存在。
grp = __vlan_find_group(real_dev->ifindex);

//如果vlan_group_hash组内还不存在当前物理接口,则进行新增,这里的
//vlan_group_hash组的作用是可以快速找到哪些物理接口含有VLAN。
if (!grp)
grp = kzalloc(sizeof(struct vlan_group), GFP_KERNEL);
 
//记载当前hash节点关联的真实物理接口索引
grp->real_dev_ifindex = real_dev->ifindex;

//将新建的组加入到vlan_group_hash的hash表中。
hlist_add_head_rcu(&grp->hlist,&vlan_group_hash[vlan_grp_hashfn(
real_dev->ifindex)]);

//如果硬件设备支持VLAN接收,则使用硬件设备的
//vlan_rx_register回调增加对该VLAN ID的接收支持。
if (real_dev->features & NETIF_F_HW_VLAN_RX)
real_dev->vlan_rx_register(real_dev, grp);

//将新建的VLAN虚拟设备加入到对应的物理组中
grp->vlan_devices[VLAN_ID] = new_dev;

vlan_proc_add_dev(new_dev)
//设备对象私有数据
dev_info = VLAN_DEV_INFO(vlandev);

//在/proc/net/vlan下创建一个条目,该条目的名字为vlan设备的名字
dev_info->dent = create_proc_entry(vlandev->name,
S_IFREG|S_IRUSR|S_IWUSR,proc_vlan_dir);

//设置proc读取的回调处理
dev_info->dent->proc_fops = &vlandev_fops;
dev_info->dent->data = vlandev;

//如果硬件支持VLAN过滤功能,则需要调用硬件的接口回调,在过滤表中
//增加新建的VLAN ID。
if (real_dev->features & NETIF_F_HW_VLAN_FILTER)
real_dev->vlan_rx_add_vid(real_dev, VLAN_ID);

<strong>三、启动VLAN接口</strong>
大体浏览了一下相关代码,这块流程比较简单,所以不做详细的分析了。主要的流程就是当用户接口输入ip link set eth0.100 up,之后会触发内核dev_change_flags函数调用,该函数会触发dev_open的调用,dev_open函数主要做了几个关键点:
a、给dev->flags新增IFF_UP标记
b、间接调用vlan_dev_open,在vlan_dev_open中并没有特殊处理,仅仅是在当前关联的真实设备是否为IFF_UP做一个检测。
c、调用dev_activate进行队列激活,之前在注册VLAN虚拟设备时设置的tx_queue_len为0,那么在dev->qdisc及dev->qdisc_sleeping都被设置为noqueue_qdisc,表示没有队列,在后继发包处理时,会检测dev->qdisc中的enqueue是否为NULL,当前noqueue_qdisc的enqueue是为NULL值,所以在发包中就不会使用队列进行处理,采用直接发送方式。

<strong>四、收包</strong>
这里仅对和VLAN相关的关键流程进行分析,其它收包细节流程可以参考《网卡驱动收包》

netif_receive_skb(struct sk_buff *skb)
......

//跳过网桥等相关处理。

//从当前报文中获取二层的负载类型,之后遍历ptype_base列表,查找是否有注册
//对应类型报文的处理。其中VLAN的支持是在上面VLAN模块初始化时已经注册,
//对应的vlan包处理函数为vlan_skb_recv
type = skb->protocol;
list_for_each_entry_rcu(ptype, &ptype_base[ntohs(type)&15], list)
if (ptype->type == type &&(!ptype->dev || ptype->dev == skb->dev))
pt_prev = ptype;

if (pt_prev)
//执行vlan_skb_recv回调
pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
vlan_skb_recv
vlan_TCI = ntohs(vhdr->h_vlan_TCI);
//获取包中的vlan id
vid = (vlan_TCI & VLAN_VID_MASK);

//注意:最初接收报文的真实物理设备,VLAN设备仅仅是虚拟设备,
//这里在当前物理设备中,根据vlan id查找对应的VLAN虚拟设备,
//并替换skb->dev值,之后会再一次进入收包流程,此后收包中的dev
//就已经变成VLAN的虚拟设备了。
skb->dev = __find_vlan_dev(dev, vid);

//当接收到VLAN报文的包,但当前设备没有对应的vlan,则做
//丢弃处理。
if (!skb->dev)
kfree_skb(skb);
return -1;

//统计处理
skb->dev->last_rx = jiffies;

stats = vlan_dev_get_stats(skb->dev);
stats->rx_packets++;
stats->rx_bytes += skb->len;

//在skb中data跳过VLAN头,同时如果硬件已经计算了校验和,
//则从校验和中去除VLAN头部分。
skb_pull_rcsum(skb, VLAN_HLEN);
skb->len -= len;
skb_postpull_rcsum(skb, skb->data, len);
return skb->data += len;

//如果接收报文的设备并不是当前VLAN接口关联的真实设备,则丢弃
//处理。
if (dev != VLAN_DEV_INFO(skb->dev)->real_dev)
kfree_skb(skb);
stats->rx_errors++;
return -1;

//从Vlan头中获取QOS优先级,这里会根据VLAN头中最高3位的值在
//ingress_priority_map表中查找优先级映射关系,默认都映射为0。
skb->priority = vlan_get_ingress_priority(skb->dev, 
ntohs(vhdr->h_vlan_TCI));

switch (skb->pkt_type)
//多播统计
case PACKET_MULTICAST:
stats->multicast++;
break;

//如果收到报文的目地MAC与真实设备不同,但与VLAN虚拟设备的
//MAC相同,则修正为是本地报文。
case PACKET_OTHERHOST:
if (!compare_ether_addr(eth_hdr(skb)->h_dest, skb->dev->dev_addr))
skb->pkt_type = PACKET_HOST;
break;

//获取VLAN上承载的三层协议类型,例如IPv4
proto = vhdr->h_vlan_encapsulated_proto;
skb->protocol = proto;

//这个字段的值如果大于等于1536,则该包是符合ethernet格式,否则
//是802.3/802.2格式或novell ehternet格式。
if (ntohs(proto) >= 1536)
//如果设备对象私有变量的flags的低位有效,则去除VLAN报头。
//这个标记默认在注册VLAN虚拟设备时设置为1。
skb = vlan_check_reorder_header(skb);
if (VLAN_DEV_INFO(skb->dev)->flags & 1)
//如果skb为共享的,或者是复制的,则需要复制一份报文,
//并将原报文释放。
if (skb_shared(skb) || skb_cloned(skb))
nskb = skb_copy(skb, GFP_ATOMIC);
kfree_skb(skb);
skb = nskb;

//修改skb报文,去除VLAN头。
if (skb)
memmove(skb->data - ETH_HLEN,
skb->data - VLAN_ETH_HLEN, 12);
skb->mac.raw += VLAN_HLEN;

return skb;

//如果重新构造skb后有效,则再次调用netif_rx进行处理,不过
//此时skb->dev的值已经为VLAN虚拟设备。流程同普通收包流程
//一样,不再进行详细分析。
if (skb)
netif_rx(skb);
else
stats->rx_errors++;
return 0;

//此时data已经指向vlan头后面
rawp = skb->data;

//该报文格式为noverl ethernet格式
if (*(unsigned short *)rawp == 0xFFFF)
skb->protocol = __constant_htons(ETH_P_802_3);

//如果设备对象私有变量的flags的低位有效,则去除VLAN报头。
//这个标记默认在注册VLAN虚拟设备时设置为1。
skb = vlan_check_reorder_header(skb);

//如果重新构造skb后有效,则再次调用netif_rx进行处理,不过
//此时skb->dev的值已经为VLAN虚拟设备。流程同普通收包流程
//一样,不再进行详细分析。
if (skb)
netif_rx(skb);
else
stats->rx_errors++;

return 0;

//除了以前两种帧格式,其余则按802.3/802.2格式处理
skb->protocol = __constant_htons(ETH_P_802_2);

skb = vlan_check_reorder_header(skb);

if (skb)
netif_rx(skb);
else
stats->rx_errors++;
Return 0;

<strong>五、发包</strong>
这里仅对和VLAN相关的关键流程进行分析,其它收包细节流程可以参考《接口设备发包》
dev_queue_xmit
......
//跳过一些GSO、分片聚合、校验合的处理。
//在上面“启动VLAN接口”里已经描述到,当前VLAN虚拟设备没有队列,所以不走
//此流程。
q = rcu_dereference(dev->qdisc);
if (q->enqueue)
//不走此流程。

//设备必须为开启才处理报文
if (dev->flags & IFF_UP)
cpu = smp_processor_id()

//当前CPU还未使用该设备发送报文
if (dev->xmit_lock_owner != cpu)
//该宏会检测当前设备是否支持无发送锁操作(NETIF_F_LLTX),如果设备
//不支持,就需要获取发送锁,同时设置xmit_lock_owner为当前CPU
HARD_TX_LOCK(dev, cpu);

//如果当前发送队列没有被关闭,则进行传输
if (!netif_queue_stopped(dev))
dev_hard_start_xmit(skb, dev)
//单个skb报文则走此流程
if (likely(!skb->next))
//netdev_nit变量为正是当用户创建AF_PACKET套接口是增加
//的,该套接口主要用于像TCPDUMP之类的监听软件使用,这
//就会把当前要发出的报文给该类监听软件传送一份。接收报文
//也有类似处理,可以参见收包处理的分析。
if (netdev_nit)
dev_queue_xmit_nit(skb, dev);

//如果当前报文是GSO类型,但当前设备不支持该特性处理,
//则在此使用软件拆分来实现。
if (netif_needs_gso(dev, skb))
//进行软件拆分处理
if (unlikely(dev_gso_segment(skb)))
//失败则丢弃报文
goto out_kfree_skb;

//通常拆分后会将有效拆分报文链接到原报文之后,这里
//判断如果拆分成功,则往向gso标签去处理,如果没有拆
//分成功,则将当前单个skb报文通过网卡发送出去。
if (skb->next)
goto gso;

//将当前单个skb报文通过设备发送回调发送出去。而这里的
//设备已经为VLAN虚拟设备,该设备的回调函数会根据当前
//真实设备是否支持VLAN发送而不同,这里我们假设当前物理
//设备不支持VLAN发送,则回调函数为
//vlan_dev_hard_start_xmit
return dev->hard_start_xmit(skb, dev);
vlan_dev_hard_start_xmit
vlan_ethhdr *veth = (struct vlan_ethhdr *)(skb->data);

//如果当前发送的报文不含VLAN头,则进行VLAN
//头添加。
if (veth->h_vlan_proto != 
__constant_htons(ETH_P_8021Q))
//检测skb->head与skb->data字段之间还有多少
//空间。
orig_headroom = skb_headroom(skb);

//统计处理
VLAN_DEV_INFO(dev)->cnt_encap_on_xmit++;

//获取用户配置的vlan id,同时将skb中的优先
//级根据用户配置的egress_priority_map表进行
//vlan优先级的映射。
veth_TCI = VLAN_DEV_INFO(dev)->vlan_id;
veth_TCI |= vlan_dev_get_egress_qos_mask(dev, skb);
//在原skb包插入VLAN头。
skb = __vlan_put_tag(skb, veth_TCI);

//统计数据
stats->tx_packets++;
stats->tx_bytes += skb->len;

//将skb->dev转换成真实设备,再次使用
//dev_queue_xmit进行发包处理,该流程同普通发送流
//程相同,这里不再详细分析。
skb->dev = VLAN_DEV_INFO(dev)->real_dev;
dev_queue_xmit(skb);

return 0;

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值