0
点赞
收藏
分享

微信扫一扫

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

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

相关推荐

0 条评论