]> git.proxmox.com Git - mirror_ubuntu-jammy-kernel.git/blobdiff - drivers/net/vxlan.c
vxlan: implement get_link_ksettings ethtool method
[mirror_ubuntu-jammy-kernel.git] / drivers / net / vxlan.c
index 3d9bcc957f7da15b58aad5d9cb6acdb9803f4553..bf04bc2e68c2f96523dd8c30c746fcd28934a150 100644 (file)
@@ -793,8 +793,7 @@ static int vxlan_gro_complete(struct sock *sk, struct sk_buff *skb, int nhoff)
        return eth_gro_complete(skb, nhoff + sizeof(struct vxlanhdr));
 }
 
-static struct vxlan_fdb *vxlan_fdb_alloc(struct vxlan_dev *vxlan,
-                                        const u8 *mac, __u16 state,
+static struct vxlan_fdb *vxlan_fdb_alloc(const u8 *mac, __u16 state,
                                         __be32 src_vni, __u16 ndm_flags)
 {
        struct vxlan_fdb *f;
@@ -835,7 +834,7 @@ static int vxlan_fdb_create(struct vxlan_dev *vxlan,
                return -ENOSPC;
 
        netdev_dbg(vxlan->dev, "add %pM -> %pIS\n", mac, ip);
-       f = vxlan_fdb_alloc(vxlan, mac, state, src_vni, ndm_flags);
+       f = vxlan_fdb_alloc(mac, state, src_vni, ndm_flags);
        if (!f)
                return -ENOMEM;
 
@@ -2487,9 +2486,11 @@ static void vxlan_xmit_one(struct sk_buff *skb, struct net_device *dev,
                vni = tunnel_id_to_key32(info->key.tun_id);
                ifindex = 0;
                dst_cache = &info->dst_cache;
-               if (info->options_len &&
-                   info->key.tun_flags & TUNNEL_VXLAN_OPT)
+               if (info->key.tun_flags & TUNNEL_VXLAN_OPT) {
+                       if (info->options_len < sizeof(*md))
+                               goto drop;
                        md = ip_tunnel_info_opts(info);
+               }
                ttl = info->key.ttl;
                tos = info->key.tos;
                label = info->key.label;
@@ -3174,9 +3175,29 @@ static void vxlan_get_drvinfo(struct net_device *netdev,
        strlcpy(drvinfo->driver, "vxlan", sizeof(drvinfo->driver));
 }
 
+static int vxlan_get_link_ksettings(struct net_device *dev,
+                                   struct ethtool_link_ksettings *cmd)
+{
+       struct vxlan_dev *vxlan = netdev_priv(dev);
+       struct vxlan_rdst *dst = &vxlan->default_dst;
+       struct net_device *lowerdev = __dev_get_by_index(vxlan->net,
+                                                        dst->remote_ifindex);
+
+       if (!lowerdev) {
+               cmd->base.duplex = DUPLEX_UNKNOWN;
+               cmd->base.port = PORT_OTHER;
+               cmd->base.speed = SPEED_UNKNOWN;
+
+               return 0;
+       }
+
+       return __ethtool_get_link_ksettings(lowerdev, cmd);
+}
+
 static const struct ethtool_ops vxlan_ethtool_ops = {
-       .get_drvinfo    = vxlan_get_drvinfo,
-       .get_link       = ethtool_op_get_link,
+       .get_drvinfo            = vxlan_get_drvinfo,
+       .get_link               = ethtool_op_get_link,
+       .get_link_ksettings     = vxlan_get_link_ksettings,
 };
 
 static struct socket *vxlan_create_sock(struct net *net, bool ipv6,
@@ -3566,10 +3587,13 @@ static int __vxlan_dev_create(struct net *net, struct net_device *dev,
 {
        struct vxlan_net *vn = net_generic(net, vxlan_net_id);
        struct vxlan_dev *vxlan = netdev_priv(dev);
+       struct net_device *remote_dev = NULL;
        struct vxlan_fdb *f = NULL;
        bool unregister = false;
+       struct vxlan_rdst *dst;
        int err;
 
+       dst = &vxlan->default_dst;
        err = vxlan_dev_configure(net, dev, conf, false, extack);
        if (err)
                return err;
@@ -3577,14 +3601,14 @@ static int __vxlan_dev_create(struct net *net, struct net_device *dev,
        dev->ethtool_ops = &vxlan_ethtool_ops;
 
        /* create an fdb entry for a valid default destination */
-       if (!vxlan_addr_any(&vxlan->default_dst.remote_ip)) {
+       if (!vxlan_addr_any(&dst->remote_ip)) {
                err = vxlan_fdb_create(vxlan, all_zeros_mac,
-                                      &vxlan->default_dst.remote_ip,
+                                      &dst->remote_ip,
                                       NUD_REACHABLE | NUD_PERMANENT,
                                       vxlan->cfg.dst_port,
-                                      vxlan->default_dst.remote_vni,
-                                      vxlan->default_dst.remote_vni,
-                                      vxlan->default_dst.remote_ifindex,
+                                      dst->remote_vni,
+                                      dst->remote_vni,
+                                      dst->remote_ifindex,
                                       NTF_SELF, &f);
                if (err)
                        return err;
@@ -3595,26 +3619,41 @@ static int __vxlan_dev_create(struct net *net, struct net_device *dev,
                goto errout;
        unregister = true;
 
+       if (dst->remote_ifindex) {
+               remote_dev = __dev_get_by_index(net, dst->remote_ifindex);
+               if (!remote_dev)
+                       goto errout;
+
+               err = netdev_upper_dev_link(remote_dev, dev, extack);
+               if (err)
+                       goto errout;
+       }
+
        err = rtnl_configure_link(dev, NULL);
        if (err)
-               goto errout;
+               goto unlink;
 
        if (f) {
-               vxlan_fdb_insert(vxlan, all_zeros_mac,
-                                vxlan->default_dst.remote_vni, f);
+               vxlan_fdb_insert(vxlan, all_zeros_mac, dst->remote_vni, f);
 
                /* notify default fdb entry */
                err = vxlan_fdb_notify(vxlan, f, first_remote_rtnl(f),
                                       RTM_NEWNEIGH, true, extack);
                if (err) {
                        vxlan_fdb_destroy(vxlan, f, false, false);
+                       if (remote_dev)
+                               netdev_upper_dev_unlink(remote_dev, dev);
                        goto unregister;
                }
        }
 
        list_add(&vxlan->next, &vn->vxlan_list);
+       if (remote_dev)
+               dst->remote_dev = remote_dev;
        return 0;
-
+unlink:
+       if (remote_dev)
+               netdev_upper_dev_unlink(remote_dev, dev);
 errout:
        /* unregister_netdevice() destroys the default FDB entry with deletion
         * notification. But the addition notification was not sent yet, so
@@ -3932,11 +3971,12 @@ static int vxlan_changelink(struct net_device *dev, struct nlattr *tb[],
                            struct netlink_ext_ack *extack)
 {
        struct vxlan_dev *vxlan = netdev_priv(dev);
-       struct vxlan_rdst *dst = &vxlan->default_dst;
        struct net_device *lowerdev;
        struct vxlan_config conf;
+       struct vxlan_rdst *dst;
        int err;
 
+       dst = &vxlan->default_dst;
        err = vxlan_nl2conf(tb, data, dev, &conf, true, extack);
        if (err)
                return err;
@@ -3946,6 +3986,14 @@ static int vxlan_changelink(struct net_device *dev, struct nlattr *tb[],
        if (err)
                return err;
 
+       if (dst->remote_dev == lowerdev)
+               lowerdev = NULL;
+
+       err = netdev_adjacent_change_prepare(dst->remote_dev, lowerdev, dev,
+                                            extack);
+       if (err)
+               return err;
+
        /* handle default dst entry */
        if (!vxlan_addr_equal(&conf.remote_ip, &dst->remote_ip)) {
                u32 hash_index = fdb_head_index(vxlan, all_zeros_mac, conf.vni);
@@ -3962,6 +4010,8 @@ static int vxlan_changelink(struct net_device *dev, struct nlattr *tb[],
                                               NTF_SELF, true, extack);
                        if (err) {
                                spin_unlock_bh(&vxlan->hash_lock[hash_index]);
+                               netdev_adjacent_change_abort(dst->remote_dev,
+                                                            lowerdev, dev);
                                return err;
                        }
                }
@@ -3979,6 +4029,11 @@ static int vxlan_changelink(struct net_device *dev, struct nlattr *tb[],
        if (conf.age_interval != vxlan->cfg.age_interval)
                mod_timer(&vxlan->age_timer, jiffies);
 
+       netdev_adjacent_change_commit(dst->remote_dev, lowerdev, dev);
+       if (lowerdev && lowerdev != dst->remote_dev) {
+               dst->remote_dev = lowerdev;
+               netdev_update_lockdep_key(lowerdev);
+       }
        vxlan_config_apply(dev, &conf, lowerdev, vxlan->net, true);
        return 0;
 }
@@ -3991,6 +4046,8 @@ static void vxlan_dellink(struct net_device *dev, struct list_head *head)
 
        list_del(&vxlan->next);
        unregister_netdevice_queue(dev, head);
+       if (vxlan->default_dst.remote_dev)
+               netdev_upper_dev_unlink(vxlan->default_dst.remote_dev, dev);
 }
 
 static size_t vxlan_get_size(const struct net_device *dev)