Linux内核中VLAN的实现过程(8)
本节主要关注和解析vlan netlink控制接口的实现,代码位于net/8021q/vlan_netlink.c
文件中。Netlink主要用于在内核和用户空间进程之间传输信息。
VLAN设备Netlink初始化
static const struct nla_policy vlan_policy[IFLA_VLAN_MAX + 1] = {
// vlan id验证策略:16位无符号整形
[IFLA_VLAN_ID] = { .type = NLA_U16 },
// vlan flags验证策略:长度为sizeof(struct ifla_vlan_flags)
[IFLA_VLAN_FLAGS] = { .len = sizeof(struct ifla_vlan_flags) },
// vlan qos验证策略:嵌套的属性
[IFLA_VLAN_EGRESS_QOS] = { .type = NLA_NESTED },
[IFLA_VLAN_INGRESS_QOS] = { .type = NLA_NESTED },
// vlan协议验证策略:16位无符号整形
[IFLA_VLAN_PROTOCOL] = { .type = NLA_U16 },
};
// vlan netlink接口操作函数定义
struct rtnl_link_ops vlan_link_ops __read_mostly = {
// 标识符
.kind = "vlan",
// netlink属性最大值
.maxtype = IFLA_VLAN_MAX,
// netlink属性验证策略
.policy = vlan_policy,
// vlan设备私有数据大小
.priv_size = sizeof(struct vlan_dev_priv),
// vlan设备setup函数
.setup = vlan_setup,
// netlink/changelink参数的可选验证函数
.validate = vlan_validate,
// 配置和注册新设备函数
.newlink = vlan_newlink,
// 修改已存在设备参数的函数
.changelink = vlan_changelink,
// 删除设备
.dellink = unregister_vlan_dev,
// 计算dump设备特定网络链接属性所需空间的函数
.get_size = vlan_get_size,
// dump设备特定网络链接属性的函数
.fill_info = vlan_fill_info,
// 获取宿主设备网络命名空间的函数
.get_link_net = vlan_get_link_net,
};
// 在vlan模块初始化中调用:vlan_proto_init()->vlan_netlink_init()
int __init vlan_netlink_init(void)
{
// 注册netlink操作函数集
return rtnl_link_register(&vlan_link_ops);
}
// 在vlan模块清理中调用:vlan_cleanup_module()->vlan_netlink_fini()
void __exit vlan_netlink_fini(void)
{
// 注销netlink操作函数集
rtnl_link_unregister(&vlan_link_ops);
}
// 模块别名(宏展开是rtnl-link-vlan),在Linux内核模块中,可以用MODULE_AUTHOR、MODULE_DESCRIPTION、MODULE_VERSION、MODULE_DEVICE_TABLE、MODULE_ALIAS分别声明模块的作者、描述、版本、设备表和别名
MODULE_ALIAS_RTNL_LINK("vlan");
配置和注册新设备
static int vlan_newlink(struct net *src_net, struct net_device *dev,
struct nlattr *tb[], struct nlattr *data[],
struct netlink_ext_ack *extack)
{
// vlan设备私有数据
struct vlan_dev_priv *vlan = vlan_dev_priv(dev);
struct net_device *real_dev;
unsigned int max_mtu;
__be16 proto;
int err;
// 校验netlink属性:vlan id
if (!data[IFLA_VLAN_ID]) {
NL_SET_ERR_MSG_MOD(extack, "VLAN id not specified");
return -EINVAL;
}
// 验证是否指定了接口索引
if (!tb[IFLA_LINK]) {
NL_SET_ERR_MSG_MOD(extack, "link not specified");
return -EINVAL;
}
// 根据指定的接口索引(nla_get_u32(tb[IFLA_LINK]))查找宿主设备
real_dev = __dev_get_by_index(src_net, nla_get_u32(tb[IFLA_LINK]));
if (!real_dev) {
NL_SET_ERR_MSG_MOD(extack, "link does not exist");
return -ENODEV;
}
// 校验netlink属性:vlan协议
if (data[IFLA_VLAN_PROTOCOL])
proto = nla_get_be16(data[IFLA_VLAN_PROTOCOL]);
else
// 未指定,则默认802.1q协议
proto = htons(ETH_P_8021Q);
// 初始化vlan设备私有数据
vlan->vlan_proto = proto;
vlan->vlan_id = nla_get_u16(data[IFLA_VLAN_ID]);
vlan->real_dev = real_dev;
dev->priv_flags |= (real_dev->priv_flags & IFF_XMIT_DST_RELEASE);
vlan->flags = VLAN_FLAG_REORDER_HDR;
// 从宿主设备vlan组中查找符合vlan协议和vlan id的设备,并校验,extract是为ack的error消息
err = vlan_check_real_dev(real_dev, vlan->vlan_proto, vlan->vlan_id,
extack);
if (err < 0)
return err;
// 计算最大mtu
max_mtu = netif_reduces_vlan_mtu(real_dev) ? real_dev->mtu - VLAN_HLEN :
real_dev->mtu;
// 没有指定mtu,则使用上面计算的mtu
if (!tb[IFLA_MTU])
dev->mtu = max_mtu;
// 校验:vlan设备mtu不可能大于宿主设备的最大mtu
else if (dev->mtu > max_mtu)
return -EINVAL;
// 修vlan设备参数
err = vlan_changelink(dev, tb, data, extack);
if (err)
return err;
// 注册vlan设备
err = register_vlan_dev(dev, extack);
if (err)
vlan_dev_free_egress_priority(dev);
return err;
}
修改已存在设备参数
static int vlan_changelink(struct net_device *dev, struct nlattr *tb[],
struct nlattr *data[],
struct netlink_ext_ack *extack)
{
struct ifla_vlan_flags *flags;
struct ifla_vlan_qos_mapping *m;
struct nlattr *attr;
int rem, err;
// 判断vlan flags是否设置
if (data[IFLA_VLAN_FLAGS]) {
// 获取vlan flags
flags = nla_data(data[IFLA_VLAN_FLAGS]);
// 修改vlan flags
err = vlan_dev_change_flags(dev, flags->flags, flags->mask);
if (err)
return err;
}
// 判断入口qos是否设置
if (data[IFLA_VLAN_INGRESS_QOS]) {
// 遍历嵌套的qos属性
nla_for_each_nested(attr, data[IFLA_VLAN_INGRESS_QOS], rem) {
// 属性payload
m = nla_data(attr);
// 设置入口优先级
vlan_dev_set_ingress_priority(dev, m->to, m->from);
}
}
// 判断出口qos是否设置
if (data[IFLA_VLAN_EGRESS_QOS]) {
// 遍历嵌套的qos属性
nla_for_each_nested(attr, data[IFLA_VLAN_EGRESS_QOS], rem) {
// 属性payload
m = nla_data(attr);
// 设置出口优先级
err = vlan_dev_set_egress_priority(dev, m->from, m->to);
if (err)
return err;
}
}
return 0;
}
删除设备
void unregister_vlan_dev(struct net_device *dev, struct list_head *head)
{
struct vlan_dev_priv *vlan = vlan_dev_priv(dev);
struct net_device *real_dev = vlan->real_dev;
struct vlan_info *vlan_info;
struct vlan_group *grp;
u16 vlan_id = vlan->vlan_id;
ASSERT_RTNL();
// 获取vlan设备信息
vlan_info = rtnl_dereference(real_dev->vlan_info);
BUG_ON(!vlan_info);
// 获取设备上的vlan组
grp = &vlan_info->grp;
// vlan个数减1
grp->nr_vlan_devs--;
// 注销garp vlan协议和multiple vlan协议
if (vlan->flags & VLAN_FLAG_MVRP)
vlan_mvrp_request_leave(dev);
if (vlan->flags & VLAN_FLAG_GVRP)
vlan_gvrp_request_leave(dev);
// 从vlan组中移除该vlan设备
vlan_group_set_device(grp, vlan->vlan_proto, vlan_id, NULL);
// 从宿主设备上删除指向vlan设备的链接,调用者必须持有RTNL锁
netdev_upper_dev_unlink(real_dev, dev);
// 此函数关闭设备接口并将其从内核表中删除。 如果head不为NULL,则设备将排队等待稍后取消注册。调用者必须持有rtnl信号量。
/* Because unregister_netdevice_queue() makes sure at least one rcu
* grace period is respected before device freeing,
* we dont need to call synchronize_net() here.
*/
unregister_netdevice_queue(dev, head);
// vlan个数为0
if (grp->nr_vlan_devs == 0) {
// 注销宿主设备上的garp vlan和multiple vlan协议应用
vlan_mvrp_uninit_applicant(real_dev);
vlan_gvrp_uninit_applicant(real_dev);
}
// 删除宿主设备上记录的该vlan id设备
vlan_vid_del(real_dev, vlan->vlan_proto, vlan_id);
}