Linux内核中VLAN的实现过程(3)
本节主要关注和解析vlan模块proc文件系统接口功能实现,代码位于
net/8021q/vlanproc.c
文件中。
接口原型
// 在proc文件系统中创建vlan条目(/proc/net/vlan)
int vlan_proc_init(struct net *net);
// 移除vlan设备节点
void vlan_proc_rem_dev(struct net_device *vlandev);
// 添加vlan设备节点
int vlan_proc_add_dev(struct net_device *vlandev);
// 从proc文件系统中删除vlan条目(/proc/net/vlan)
void vlan_proc_cleanup(struct net *net);
创建vlan条目
root@penyforever-pc:~# vconfig add docker0 1
root@penyforever-pc:~# vconfig add enp2s0 1
root@penyforever-pc:~# cd /proc/net/vlan
root@penyforever-pc:/proc/net/vlan# ls
config docker0.1 enp2s0.1
root@penyforever-pc:/proc/net/vlan# cat config
VLAN Dev name | VLAN ID
Name-Type: VLAN_NAME_TYPE_RAW_PLUS_VID_NO_PAD
enp2s0.1 | 1 | enp2s0
docker0.1 | 1 | docker0
root@penyforever-pc:/proc/net/vlan# cat docker0.1
docker0.1 VID: 1 REORDER_HDR: 1 dev->priv_flags: 1001
total frames received 0
total bytes received 0
Broadcast/Multicast Rcvd 0
total frames transmitted 0
total bytes transmitted 0
Device: docker0
INGRESS priority mappings: 0:0 1:0 2:0 3:0 4:0 5:0 6:0 7:0
EGRESS priority mappings:
root@penyforever-pc:/proc/net/vlan# cat enp2s0.1
enp2s0.1 VID: 1 REORDER_HDR: 1 dev->priv_flags: 1021
total frames received 0
total bytes received 0
Broadcast/Multicast Rcvd 0
total frames transmitted 0
total bytes transmitted 0
Device: enp2s0
INGRESS priority mappings: 0:0 1:0 2:0 3:0 4:0 5:0 6:0 7:0
EGRESS priority mappings:
/*
* Create /proc/net/vlan entries
*/
int __net_init vlan_proc_init(struct net *net)
{
// 根据vlan命名空间id获取vlan proc文件系统信息
struct vlan_net *vn = net_generic(net, vlan_net_id);
// 在/proc/net/下创建vlan目录
vn->proc_vlan_dir = proc_net_mkdir(net, name_root, net->proc_net);
if (!vn->proc_vlan_dir)
goto err;
// 在/proc/net/vlan/下创建config文件,并设置文件权限和文件(seq fie)操作函数
vn->proc_vlan_conf = proc_create_net(name_conf, S_IFREG | 0600,
vn->proc_vlan_dir, &vlan_seq_ops,
sizeof(struct seq_net_private));
if (!vn->proc_vlan_conf)
goto err;
return 0;
err:
pr_err("can't create entry in proc filesystem!\n");
从proc文件系统中删除vlan条目(/proc/net/vlan)
vlan_proc_cleanup(net);
return -ENOBUFS;
}
删除vlan条目
/*
* Clean up /proc/net/vlan entries
*/
void vlan_proc_cleanup(struct net *net)
{
// 根据vlan命名空间id获取vlan proc文件系统信息
struct vlan_net *vn = net_generic(net, vlan_net_id);
// 删除config文件
if (vn->proc_vlan_conf)
remove_proc_entry(name_conf, vn->proc_vlan_dir);
// 删除vlan目录
if (vn->proc_vlan_dir)
remove_proc_entry(name_root, net->proc_net);
/* Dynamically added entries should be cleaned up as their vlan_device
* is removed, so we should not have to take care of it here...
*/
}
添加设备节点
/*
* Add directory entry for VLAN device.
*/
int vlan_proc_add_dev(struct net_device *vlandev)
{
// 获取vlan设备私有信息
struct vlan_dev_priv *vlan = vlan_dev_priv(vlandev);
// 根据vlan命名空间id获取vlan proc文件系统信息
struct vlan_net *vn = net_generic(dev_net(vlandev), vlan_net_id);
// 校验,vlan设备名字不能是“config”
if (!strcmp(vlandev->name, name_conf))
return -EINVAL;
// 在/proc/vlan/下创建设备节点文件,用来保存vlan设备信息,并设置文件权限和文件(seq file)操作函数
vlan->dent = proc_create_single_data(vlandev->name, S_IFREG | 0600,
vn->proc_vlan_dir, vlandev_seq_show, vlandev);
if (!vlan->dent)
return -ENOBUFS;
return 0;
}
移除设备节点
/*
* Delete directory entry for VLAN device.
*/
void vlan_proc_rem_dev(struct net_device *vlandev)
{
// 从/proc/net/vlan/移除vlan设备信息文件
/** NOTE: This will consume the memory pointed to by dent, it seems. */
proc_remove(vlan_dev_priv(vlandev)->dent);
// vlan设备私有信息置为空
vlan_dev_priv(vlandev)->dent = NULL;
}
seq文件操作函数
/****** Proc filesystem entry points ****************************************/
/*
* The following few functions build the content of /proc/net/vlan/config
*/
// 主要实现初始化工作,在遍历一个链接对象开始时调用,返回一个链接对象的偏移或者SEQ_START_TOKEN(表征这是所有循环的开始)
/* start read of /proc/net/vlan/config */
static void *vlan_seq_start(struct seq_file *seq, loff_t *pos)
__acquires(rcu)
{
struct net_device *dev;
struct net *net = seq_file_net(seq);
loff_t i = 1;
// 加rcu读锁
rcu_read_lock();
if (*pos == 0)
return SEQ_START_TOKEN;
// 遍历网络命名空间下的所有设备,找到偏移pos指向的vlan设备
for_each_netdev_rcu(net, dev) {
if (!is_vlan_dev(dev))
continue;
if (i++ == *pos)
return dev;
}
return NULL;
}
// 用来在遍历中寻找下一个链接对象,返回下一个链接对象或者NULL(遍历结束)
static void *vlan_seq_next(struct seq_file *seq, void *v, loff_t *pos)
{
struct net_device *dev;
struct net *net = seq_file_net(seq);
++*pos;
// 找到下一个vlan设备
dev = v;
if (v == SEQ_START_TOKEN)
dev = net_device_entry(&net->dev_base_head);
for_each_netdev_continue_rcu(net, dev) {
if (!is_vlan_dev(dev))
continue;
return dev;
}
return NULL;
}
// 当所有链接对象遍历结束时调用,主要完成一些清理工作
static void vlan_seq_stop(struct seq_file *seq, void *v)
__releases(rcu)
{
// 释放rcu读锁
rcu_read_unlock();
}
// 对遍历对象进行查看操作的函数,主要是调用seq_printf(), seq_puts()之类的函数,打印出这个对象节点的信息
static int vlandev_seq_show(struct seq_file *seq, void *offset)
{
// 获取vlan设备信息
struct net_device *vlandev = (struct net_device *) seq->private;
// 获取vlan设备私有信息
const struct vlan_dev_priv *vlan = vlan_dev_priv(vlandev);
struct rtnl_link_stats64 temp;
const struct rtnl_link_stats64 *stats;
static const char fmt64[] = "%30s %12llu\n";
int i;
if (!is_vlan_dev(vlandev))
return 0;
// 获取vlan设备接口统计信息
stats = dev_get_stats(vlandev, &temp);
// 输出设备设备名称,vlan id,网络设备接口的标识符(其状态类型被定义在<linux/if.h>之中),网络设备接口的标识符(但对用户空间不可见)
seq_printf(seq,
"%s VID: %d REORDER_HDR: %i dev->priv_flags: %llx\n",
vlandev->name, vlan->vlan_id,
(int)(vlan->flags & 1), vlandev->priv_flags);
// 输出接口收到的数据包个数
seq_printf(seq, fmt64, "total frames received", stats->rx_packets);
// 输出接口收到的字节数
seq_printf(seq, fmt64, "total bytes received", stats->rx_bytes);
// 输出接口收到的广播包和组播包个数
seq_printf(seq, fmt64, "Broadcast/Multicast Rcvd", stats->multicast);
seq_puts(seq, "\n");
// 输出接口发送的数据包个数
seq_printf(seq, fmt64, "total frames transmitted", stats->tx_packets);
// 输出接口发送的字节数
seq_printf(seq, fmt64, "total bytes transmitted", stats->tx_bytes);
// 输出vlan设备宿主设备名称
seq_printf(seq, "Device: %s", vlan->real_dev->name);
// 输出入口vlan优先级信息
/* now show all PRIORITY mappings relating to this VLAN */
seq_printf(seq, "\nINGRESS priority mappings: "
"0:%u 1:%u 2:%u 3:%u 4:%u 5:%u 6:%u 7:%u\n",
vlan->ingress_priority_map[0],
vlan->ingress_priority_map[1],
vlan->ingress_priority_map[2],
vlan->ingress_priority_map[3],
vlan->ingress_priority_map[4],
vlan->ingress_priority_map[5],
vlan->ingress_priority_map[6],
vlan->ingress_priority_map[7]);
// 输出出口vlan优先级信息
seq_printf(seq, " EGRESS priority mappings: ");
for (i = 0; i < 16; i++) {
const struct vlan_priority_tci_mapping *mp
= vlan->egress_priority_map[i];
while (mp) {
seq_printf(seq, "%u:%d ",
mp->priority, ((mp->vlan_qos >> 13) & 0x7));
mp = mp->next;
}
}
seq_puts(seq, "\n");
return 0;
}