添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
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

对于IPv4,在初始化时,为命名空间的设备地址标识赋于一个随机值。static __net_init int rt_genid_init(struct net *net){ atomic_set(&amp;net-&gt;ipv4.rt_genid, 0); atomic_set(&amp;net-&gt;fnhe_genid, 0); atomic_set(&amp;net-&gt;ipv4.dev_addr_genid, get_random_int()); retu
文章目录前言1 NetID 与 DevAddr前缀的关系2 DevAddr 列举3 小结End DevAddr 标识入网设备地址,v1.1 的核心规范开始关注网络漫游,联盟将DevAddr管控起来,高级别成员享受到较多的设备地址数量。 《LoRaWAN-Backend-Interfaces-v1.0》,即LoRaWAN后端接口协议规范 V1.0 版本( 2017 年 10 月 11 日定稿...
什么是NAPI NAPI是linux一套最新的处理网口数据的API,linux 2.5引入的,所以很多驱动并不支持这种操作方式。简单来说,NAPI是综合中断方式与轮询方式的技术。数据量很低与很高时,NAPI可以发挥中断方式与轮询方式的优点,性能较好。如果数据量不稳定,且说高不高说低不低,则NAPI会在两种方式切换上消耗不少时间,效率反而较低一些。 下面会用到netdev_priv()这个
DAD冲突检测和IPv6地址失效检测使用的都是addrconf_wq队列,其在addrconf_init函数中创建。 int __init addrconf_init(void) struct inet6_dev *idev; addrconf_wq = create_workqueue("ipv6_addrconf"); if (!addrconf_wq) { err = -ENOMEM; goto out_nowq;
内核中为网络设备定义了5中MAC地址类型,如下所示,其中NETDEV_HW_ADDR_T_SLAVE类型目前没有使用。局域网LAN类型与存储SAN类型的MAC地址保存在net_device结构体的dev_addrs链表中;UNICAST与MULTICAST类型的MAC地址分别保存在uc和mc链表中。 #define NETDEV_HW_ADDR_T_LAN        1    // Loca...
上一节描述了snull网络接口的入口和出口函数,在入口函数分配一个net_device,用snull_init函数初始化net_device的成员,之后注册这个网络设备。初始化net_device成员主要是实现一些设备操作方法,如下所示: static const struct net_device_ops snull_dev_ops = { .ndo_open = s...
用C语言写出int16_t i2c_read_reg(uint8_t dev_addr, uint8_t reg_addr, uint8_t *data, uint16_t size)