2

Linux内核中VLAN的实现过程(8)-Netlink控制接口

 1 year ago
source link: https://blog.51cto.com/u_15109148/5445377
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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);
}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK