命名空间设备地址标识dev_addr_genid

IPv6 专栏收录该内容
22 篇文章 0 订阅

对于IPv4,在初始化时,为命名空间的设备地址标识赋于一个随机值。

static __net_init int rt_genid_init(struct net *net)
{
    atomic_set(&net->ipv4.rt_genid, 0);
    atomic_set(&net->fnhe_genid, 0);
    atomic_set(&net->ipv4.dev_addr_genid, get_random_int());
    return 0;
}

static __net_initdata struct pernet_operations rt_genid_ops = {
    .init = rt_genid_init,
};

IPv4地址标识

如下fib_inetaddr_event函数,当添加或者删除一个接口地址时,都会递增设备所在命名空间的地址标识。

static int fib_inetaddr_event(struct notifier_block *this, unsigned long event, void *ptr)
{
    struct in_ifaddr *ifa = (struct in_ifaddr *)ptr;
    struct net_device *dev = ifa->ifa_dev->dev;
    struct net *net = dev_net(dev);

    switch (event) {
    case NETDEV_UP:
        fib_add_ifaddr(ifa);
#ifdef CONFIG_IP_ROUTE_MULTIPATH
        fib_sync_up(dev, RTNH_F_DEAD);
#endif
        atomic_inc(&net->ipv4.dev_addr_genid);
        rt_cache_flush(dev_net(dev));
        break;
    case NETDEV_DOWN:
        fib_del_ifaddr(ifa, NULL);
        atomic_inc(&net->ipv4.dev_addr_genid);

当接口up时,也会递增接口所在命名空间的地址标识。但是,当接口down时,并没有删除IP地址,不操作地址标识。

static int fib_netdev_event(struct notifier_block *this, unsigned long event, void *ptr)
{
    struct net_device *dev = netdev_notifier_info_to_dev(ptr);
    struct net *net = dev_net(dev);
    ...

    switch (event) {
    case NETDEV_UP:
        in_dev_for_each_ifa_rtnl(ifa, in_dev) {
            fib_add_ifaddr(ifa);
        }
#ifdef CONFIG_IP_ROUTE_MULTIPATH
        fib_sync_up(dev, RTNH_F_DEAD);
#endif
        atomic_inc(&net->ipv4.dev_addr_genid);
        rt_cache_flush(net);
        break;
    case NETDEV_DOWN:
        fib_disable_ip(dev, event, false);
        break;

IPv6地址标识

IPv6函数__ipv6_ifa_notify统一处理地址的添加和删除事件,无论是哪种事件,都涉及到地址的操作,最后需要递增地址标识。

static void __ipv6_ifa_notify(int event, struct inet6_ifaddr *ifp)
{
    struct net *net = dev_net(ifp->idev->dev);

    inet6_ifa_notify(event ? : RTM_NEWADDR, ifp);

    switch (event) {
    case RTM_NEWADDR:
        ...
        break;
    case RTM_DELADDR:
        ...
        break;
    }
    atomic_inc(&net->ipv6.dev_addr_genid);

获取IPv4地址

在获取内核网络设备地址时,如ip address show命令,netlink_callback回调结构体的序号成员变量seq,设置为网络命名空间中ipv4.dev_addr_genid和dev_base_seq异或的值,这样即兼顾地址和接口两者的变化。在函数in_dev_dump_addr中(其子函数nl_dump_check_consistent),将检查seq是否改变,如果在for循环过程中,seq发生变化,表明地址或者接口有变化,本次dump取到的信息可能不是最新的,需要通知应用层。

static int inet_dump_ifaddr(struct sk_buff *skb, struct netlink_callback *cb)
{
    s_h = cb->args[0];
    s_idx = idx = cb->args[1];
    s_ip_idx = cb->args[2];

    for (h = s_h; h < NETDEV_HASHENTRIES; h++, s_idx = 0) {
        idx = 0;
        head = &tgt_net->dev_index_head[h];
        rcu_read_lock();
        cb->seq = atomic_read(&tgt_net->ipv4.dev_addr_genid) ^
              tgt_net->dev_base_seq;
        ...
            err = in_dev_dump_addr(in_dev, skb, cb, s_ip_idx, &fillargs);

在函数nl_dump_check_consistent中,通过NLM_F_DUMP_INTR标志通知上层,取到的数据可能不完整。

static inline void
nl_dump_check_consistent(struct netlink_callback *cb, struct nlmsghdr *nlh)
{   
    if (cb->prev_seq && cb->seq != cb->prev_seq)
        nlh->nlmsg_flags |= NLM_F_DUMP_INTR;
    cb->prev_seq = cb->seq;

获取IPv6地址

在获取内核IPv6地址信息时,序号seq的值初始化为命名空间内IPv6地址标识ipv6.dev_addr_genid和接口标识dev_base_seq的异或值。这里有个问题,如何保证cb->seq的值不为零?如果目标命名空间中,ipv6.dev_addr_genid的值等于dev_base_seq的值,将导致seq为零。虽然这个概率很小,但是一旦发生,将导致nl_dump_check_consistent函数出错。

static int inet6_dump_addr(struct sk_buff *skb, struct netlink_callback *cb, enum addr_type_t type)
{
    struct net *net = sock_net(skb->sk);
    struct net *tgt_net = net;

    cb->seq = atomic_read(&tgt_net->ipv6.dev_addr_genid) ^ tgt_net->dev_base_seq;
    for (h = s_h; h < NETDEV_HASHENTRIES; h++, s_idx = 0) {
        idx = 0;
        head = &tgt_net->dev_index_head[h];
        hlist_for_each_entry_rcu(dev, head, index_hlist) {
            if (idx < s_idx)
                goto cont;
            if (h > s_h || idx > s_idx)
                s_ip_idx = 0;
            idev = __in6_dev_get(dev);
            if (!idev)
                goto cont;

            if (in6_dump_addrs(idev, skb, cb, s_ip_idx, &fillargs) < 0)
                goto done;

如下in6_dump_addrs函数,只有在遍历单播地址过程中,才会调用nl_dump_check_consistent函数,检查序号seq是否发生改变。

static int in6_dump_addrs(struct inet6_dev *idev, struct sk_buff *skb,
              struct netlink_callback *cb, int s_ip_idx, struct inet6_fill_args *fillargs)
{
    ...
    switch (fillargs->type) {
    case UNICAST_ADDR: {
        struct inet6_ifaddr *ifa;
        fillargs->event = RTM_NEWADDR;

        /* unicast address incl. temp addr */
        list_for_each_entry(ifa, &idev->addr_list, if_list) {
            if (ip_idx < s_ip_idx)
                goto next;
            err = inet6_fill_ifaddr(skb, ifa, fillargs);
            if (err < 0)
                break;
            nl_dump_check_consistent(cb, nlmsg_hdr(skb));
next:
            ip_idx++;
        }
        break;
    }
    case MULTICAST_ADDR:
        ...
        break;
    case ANYCAST_ADDR:

内核版本 5.10

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值