网络命名空间初始化时,将序号dev_base_seq赋值为1。
static __net_init int setup_net(struct net *net, struct user_namespace *user_ns)
{
/* Must be called with pernet_ops_rwsem held */
const struct pernet_operations *ops, *saved_ops;
int error = 0;
LIST_HEAD(net_exit_list);
refcount_set(&net->count, 1);
refcount_set(&net->passive, 1);
get_random_bytes(&net->hash_mix, sizeof(u32));
net->dev_base_seq = 1;
网络命名空间中每增加一个设备,dev_base_seq的值递增1,如下函数list_netdevice所示,函数dev_base_seq_inc完成dev_base_seq的递增。
/* Device list insertion */
static void list_netdevice(struct net_device *dev)
{
struct net *net = dev_net(dev);
ASSERT_RTNL();
write_lock_bh(&dev_base_lock);
list_add_tail_rcu(&dev->dev_list, &net->dev_base_head);
netdev_name_node_add(net, dev->name_node);
hlist_add_head_rcu(&dev->index_hlist, dev_index_hash(net, dev->ifindex));
write_unlock_bh(&dev_base_lock);
dev_base_seq_inc(net);
由网络命名空间中注销设备时,也是调用函数dev_base_seq_inc递增dev_base_seq的值。
/* Device list removal
* caller must respect a RCU grace period before freeing/reusing dev
*/
static void unlist_netdevice(struct net_device *dev)
{
ASSERT_RTNL();
/* Unlink dev from the device chain */
write_lock_bh(&dev_base_lock);
list_del_rcu(&dev->dev_list);
netdev_name_node_del(dev->name_node);
hlist_del_rcu(&dev->index_hlist);
write_unlock_bh(&dev_base_lock);
dev_base_seq_inc(dev_net(dev));
在改变设备的命名空间时,旧的命名空间和新的命名空间中的dev_base_seq的值都进行递增。
int dev_change_net_namespace(struct net_device *dev, struct net *net, const char *pat)
{
/* And unlink it from device chain */
unlist_netdevice(dev);
/* Add the device back in the hashes */
list_netdevice(dev);
如下为dev_base_seq的递增函数,网络命名空间中的dev_base_seq的值如果递增之后等于0,将其值设置为1。
static inline void dev_base_seq_inc(struct net *net)
{
while (++net->dev_base_seq == 0)
;
}
在获取内核网络设备时,如ip link show命令,内核函数rtnl_dump_ifinfo在最后,将记录下命名空间net中的dev_base_seq,此处的net是套接口所属的命名空间,而不是目标命名空间tgt_net。如果用户没有指定命名空间(IFLA_TARGET_NETNSID),二者是相同的;但是,如果用户指定了另外的命名空间,二者的值不相同。
如果命名空间中接口比较多,rtnl_fill_ifinfo返回值小于零,skb->len中为当前的数据长度,跳出循环,应用层应当在当前位置再次进行接收,在应用层的多次调用之间,网络命名空间中的接口可能发生变化,将可能导致应用层取不到全部正确的接口。函数nl_dump_check_consistent用来检查这一情况。
函数最后使用套接口所属命名空间中的dev_base_seq序号,如果tgt_net中的接口发生改变,是不是不能检测到?
static int rtnl_dump_ifinfo(struct sk_buff *skb, struct netlink_callback *cb)
{
struct net *net = sock_net(skb->sk);
struct net *tgt_net = net;
...
for (h = s_h; h < NETDEV_HASHENTRIES; h++, s_idx = 0) {
idx = 0;
head = &tgt_net->dev_index_head[h];
hlist_for_each_entry(dev, head, index_hlist) {
...
err = rtnl_fill_ifinfo(...);
if (err < 0) {
if (likely(skb->len))
goto out;
goto out_err;
}
...
}
out:
err = skb->len;
out_err:
cb->args[1] = idx;
cb->args[0] = h;
cb->seq = net->dev_base_seq;
nl_dump_check_consistent(cb, nlmsg_hdr(skb));
如下nl_dump_check_consistent函数,初始情况下,prev_seq为零,将seq赋值给prev_seq。在以上函数dev_base_seq_inc中看到dev_base_seq不为零,如果其为零,将其赋值给prev_seq,当seq后续改变,不等于零时,还会重新给prev_seq赋值,不能检测到接口变化,无法通过设置NLM_F_DUMP_INTR标志通知上层应用。
如果seq发生改变,通过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;
内核版本 5.10