rtnetlink: Add peer_type in struct rtnl_link_ops.
authorKuniyuki Iwashima <kuniyu@amazon.com>
Fri, 8 Nov 2024 00:48:18 +0000 (16:48 -0800)
committerJakub Kicinski <kuba@kernel.org>
Tue, 12 Nov 2024 01:26:52 +0000 (17:26 -0800)
In ops->newlink(), veth, vxcan, and netkit call rtnl_link_get_net() with
a net pointer, which is the first argument of ->newlink().

rtnl_link_get_net() could return another netns based on IFLA_NET_NS_PID
and IFLA_NET_NS_FD in the peer device's attributes.

We want to get it and fill rtnl_nets->nets[] in advance in rtnl_newlink()
for per-netns RTNL.

All of the three get the peer netns in the same way:

  1. Call rtnl_nla_parse_ifinfomsg()
  2. Call ops->validate() (vxcan doesn't have)
  3. Call rtnl_link_get_net_tb()

Let's add a new field peer_type to struct rtnl_link_ops and prefetch
netns in the peer ifla to add it to rtnl_nets in rtnl_newlink().

Signed-off-by: Kuniyuki Iwashima <kuniyu@amazon.com>
Reviewed-by: Eric Dumazet <edumazet@google.com>
Reviewed-by: Nikolay Aleksandrov <razor@blackwall.org>
Link: https://patch.msgid.link/20241108004823.29419-6-kuniyu@amazon.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
include/net/rtnetlink.h
net/core/rtnetlink.c

index ef7c11f..bef76ab 100644 (file)
@@ -75,6 +75,7 @@ static inline int rtnl_msg_family(const struct nlmsghdr *nlh)
  *     @srcu: Used internally
  *     @kind: Identifier
  *     @netns_refund: Physical device, move to init_net on netns exit
+ *     @peer_type: Peer device specific netlink attribute number (e.g. VETH_INFO_PEER)
  *     @maxtype: Highest device specific netlink attribute number
  *     @policy: Netlink policy for device specific attribute validation
  *     @validate: Optional validation function for netlink/changelink parameters
@@ -116,6 +117,7 @@ struct rtnl_link_ops {
        void                    (*setup)(struct net_device *dev);
 
        bool                    netns_refund;
+       const u16               peer_type;
        unsigned int            maxtype;
        const struct nla_policy *policy;
        int                     (*validate)(struct nlattr *tb[],
index 960d9d2..1af187a 100644 (file)
@@ -2492,9 +2492,10 @@ int rtnl_nla_parse_ifinfomsg(struct nlattr **tb, const struct nlattr *nla_peer,
 }
 EXPORT_SYMBOL(rtnl_nla_parse_ifinfomsg);
 
-struct net *rtnl_link_get_net(struct net *src_net, struct nlattr *tb[])
+static struct net *rtnl_link_get_net_ifla(struct nlattr *tb[])
 {
-       struct net *net;
+       struct net *net = NULL;
+
        /* Examine the link attributes and figure out which
         * network namespace we are talking about.
         */
@@ -2502,8 +2503,17 @@ struct net *rtnl_link_get_net(struct net *src_net, struct nlattr *tb[])
                net = get_net_ns_by_pid(nla_get_u32(tb[IFLA_NET_NS_PID]));
        else if (tb[IFLA_NET_NS_FD])
                net = get_net_ns_by_fd(nla_get_u32(tb[IFLA_NET_NS_FD]));
-       else
+
+       return net;
+}
+
+struct net *rtnl_link_get_net(struct net *src_net, struct nlattr *tb[])
+{
+       struct net *net = rtnl_link_get_net_ifla(tb);
+
+       if (!net)
                net = get_net(src_net);
+
        return net;
 }
 EXPORT_SYMBOL(rtnl_link_get_net);
@@ -3765,6 +3775,37 @@ out_unregister:
        goto out;
 }
 
+static int rtnl_add_peer_net(struct rtnl_nets *rtnl_nets,
+                            const struct rtnl_link_ops *ops,
+                            struct nlattr *data[],
+                            struct netlink_ext_ack *extack)
+{
+       struct nlattr *tb[IFLA_MAX + 1];
+       struct net *net;
+       int err;
+
+       if (!data || !data[ops->peer_type])
+               return 0;
+
+       err = rtnl_nla_parse_ifinfomsg(tb, data[ops->peer_type], extack);
+       if (err < 0)
+               return err;
+
+       if (ops->validate) {
+               err = ops->validate(tb, NULL, extack);
+               if (err < 0)
+                       return err;
+       }
+
+       net = rtnl_link_get_net_ifla(tb);
+       if (IS_ERR(net))
+               return PTR_ERR(net);
+       if (net)
+               rtnl_nets_add(rtnl_nets, net);
+
+       return 0;
+}
+
 static int __rtnl_newlink(struct sk_buff *skb, struct nlmsghdr *nlh,
                          const struct rtnl_link_ops *ops,
                          struct net *tgt_net, struct net *link_net,
@@ -3893,12 +3934,18 @@ static int rtnl_newlink(struct sk_buff *skb, struct nlmsghdr *nlh,
                        if (ret < 0)
                                goto put_ops;
                }
+
+               if (ops->peer_type) {
+                       ret = rtnl_add_peer_net(&rtnl_nets, ops, data, extack);
+                       if (ret < 0)
+                               goto put_ops;
+               }
        }
 
        tgt_net = rtnl_link_get_net_capable(skb, sock_net(skb->sk), tb, CAP_NET_ADMIN);
        if (IS_ERR(tgt_net)) {
                ret = PTR_ERR(tgt_net);
-               goto put_ops;
+               goto put_net;
        }
 
        rtnl_nets_add(&rtnl_nets, tgt_net);