Linux内核中VLAN的实现过程(6)
本节主要关注和解析vlan设备的打开、关闭以及数据发送实现,代码位于net/8021q/vlan_dev.c
文件中。
打开设备
static int vlan_dev_open(struct net_device *dev)
{
// 获取vlan设备的私有信息
struct vlan_dev_priv *vlan = vlan_dev_priv(dev);
// 宿主设备
struct net_device *real_dev = vlan->real_dev;
int err;
if (!(real_dev->flags & IFF_UP) &&
!(vlan->flags & VLAN_FLAG_LOOSE_BINDING))
return -ENETDOWN;
// 检查设备mac地址:vlan设备mac地址等于宿主设备mac地址
if (!ether_addr_equal(dev->dev_addr, real_dev->dev_addr) &&
!vlan_dev_inherit_address(dev, real_dev)) {
// 为vlan设备添加第二个单播mac地址(Add a secondary unicast address to the device or increase the reference count if it already exists.)
err = dev_uc_add(real_dev, dev->dev_addr);
if (err < 0)
goto out;
}
// allmulti如promiscuity一样,是一个计数器而不是简单的布尔值。当此变量由0变为非0时,就会调用dev_set_allmulti函数,以指示该端口监听所有的多播地址;当allmulti变为0时则反之。
if (dev->flags & IFF_ALLMULTI) {
// 设备监听所有多播地址
err = dev_set_allmulti(real_dev, 1);
if (err < 0)
goto del_unicast;
}
if (dev->flags & IFF_PROMISC) {
// 开启混杂模式
err = dev_set_promiscuity(real_dev, 1);
if (err < 0)
goto clear_allmulti;
}
// 宿主设备的mac保存到vlan设备私有信息中
ether_addr_copy(vlan->real_dev_addr, real_dev->dev_addr);
// 加入GARP VLAN注册协议
if (vlan->flags & VLAN_FLAG_GVRP)
vlan_gvrp_request_join(dev);
// 加入Multiple VLAN注册协议
if (vlan->flags & VLAN_FLAG_MVRP)
vlan_mvrp_request_join(dev);
/*
网卡在物理上具有载波侦听的功能,当网络连接完整或者网络链接断开时,网卡芯片硬件会自动设置寄存器标志位来标识。
如网线链接断开的时候,会将LinkSts清位;重新链接网线,则硬件自动将此位置位。
这样,在网卡驱动中读写该位信息就可一判断网络是否链接通路。
网卡驱动程序通过netif_carrier_on/netif_carrier_off/netif_carrier_ok来和内核网络子系统传递信息。
netif_carrier_on:告诉内核子系统网络链接完整。
netif_carrier_off:告诉内核子系统网络断开。
netif_carrier_ok:查询网络断开还是链接。
*/
// 设置网络连接
if (netif_carrier_ok(real_dev) &&
!(vlan->flags & VLAN_FLAG_BRIDGE_BINDING))
netif_carrier_on(dev);
return 0;
clear_allmulti:
if (dev->flags & IFF_ALLMULTI)
dev_set_allmulti(real_dev, -1);
del_unicast:
if (!ether_addr_equal(dev->dev_addr, real_dev->dev_addr))
dev_uc_del(real_dev, dev->dev_addr);
out:
// 设置网络断开
netif_carrier_off(dev);
return err;
}
关闭设备
static int vlan_dev_stop(struct net_device *dev)
{
struct vlan_dev_priv *vlan = vlan_dev_priv(dev);
struct net_device *real_dev = vlan->real_dev;
// 删除由 dev_mc_sync() 添加到目标设备的所有地址
dev_mc_unsync(real_dev, dev);
// 删除由 dev_uc_sync() 添加到目标设备的所有地址
dev_uc_unsync(real_dev, dev);
// 关闭多播
if (dev->flags & IFF_ALLMULTI)
dev_set_allmulti(real_dev, -1);
// 关闭混杂
if (dev->flags & IFF_PROMISC)
dev_set_promiscuity(real_dev, -1);
// 移除单播mac地址
if (!ether_addr_equal(dev->dev_addr, real_dev->dev_addr))
dev_uc_del(real_dev, dev->dev_addr);
// 设置网络断开
if (!(vlan->flags & VLAN_FLAG_BRIDGE_BINDING))
netif_carrier_off(dev);
return 0;
}
发送数据
static netdev_tx_t vlan_dev_hard_start_xmit(struct sk_buff *skb,
struct net_device *dev)
{
struct vlan_dev_priv *vlan = vlan_dev_priv(dev);
// 带vlan的以太网头
struct vlan_ethhdr *veth = (struct vlan_ethhdr *)(skb->data);
unsigned int len;
int ret;
/* 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 != vlan->vlan_proto ||
vlan->flags & VLAN_FLAG_REORDER_HDR) {
// 无vlan tag,则添加vlan tag
u16 vlan_tci;
vlan_tci = vlan->vlan_id;
// 获取出口qos优先级
vlan_tci |= vlan_dev_get_egress_qos_mask(dev, skb->priority);
// 添加vlan tag
__vlan_hwaccel_put_tag(skb, vlan->vlan_proto, vlan_tci);
}
// 修改数据包发送网卡为宿主设备
skb->dev = vlan->real_dev;
len = skb->len;
// 如果ndo_start_xmit是由netpool调用,则返回非零值
if (unlikely(netpoll_tx_running(dev)))
// 使用netpool发送数据包
return vlan_netpoll_send_skb(vlan, skb);
// 将数据包加入发送队列
ret = dev_queue_xmit(skb);
// dev_queue_xmit返回成功或者拥塞
if (likely(ret == NET_XMIT_SUCCESS || ret == NET_XMIT_CN)) {
struct vlan_pcpu_stats *stats;
stats = this_cpu_ptr(vlan->vlan_pcpu_stats);
// 序列计数器
u64_stats_update_begin(&stats->syncp);
// 更新设备统计信息:发包个数和发包字节数
stats->tx_packets++;
stats->tx_bytes += len;
u64_stats_update_end(&stats->syncp);
} else {
// 丢弃,并更新设备统计信息:丢包个数
this_cpu_inc(vlan->vlan_pcpu_stats->tx_dropped);
}
return ret;
}
static inline netdev_tx_t vlan_netpoll_send_skb(struct vlan_dev_priv *vlan, struct sk_buff *skb)
{
#ifdef CONFIG_NET_POLL_CONTROLLER
// 使用netpool发送数据包
return netpoll_send_skb(vlan->netpoll, skb);
#else
BUG();
return NETDEV_TX_OK;
#endif
}