Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8bcdc4f3 authored by Roopa Prabhu's avatar Roopa Prabhu Committed by David S. Miller
Browse files

vxlan: add changelink support



This patch adds changelink rtnl op support for vxlan netdevs.
code changes involve:
    - refactor vxlan_newlink into vxlan_nl2conf to be
    used by vxlan_newlink and vxlan_changelink
    - vxlan_nl2conf and vxlan_dev_configure take a
    changelink argument to isolate changelink checks
    and updates.
    - Allow changing only a few attributes:
        - return -EOPNOTSUPP for attributes that cannot
        be changed for now. Incremental patches can
        make the non-supported one available in the future
        if needed.

Signed-off-by: default avatarRoopa Prabhu <roopa@cumulusnetworks.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent d1892e4e
Loading
Loading
Loading
Loading
+270 −113
Original line number Diff line number Diff line
@@ -2835,28 +2835,29 @@ static int vxlan_sock_add(struct vxlan_dev *vxlan)
}

static int vxlan_dev_configure(struct net *src_net, struct net_device *dev,
			       struct vxlan_config *conf)
			       struct vxlan_config *conf,
			       bool changelink)
{
	struct vxlan_net *vn = net_generic(src_net, vxlan_net_id);
	struct vxlan_dev *vxlan = netdev_priv(dev), *tmp;
	struct vxlan_rdst *dst = &vxlan->default_dst;
	unsigned short needed_headroom = ETH_HLEN;
	int err;
	bool use_ipv6 = false;
	__be16 default_port = vxlan->cfg.dst_port;
	struct net_device *lowerdev = NULL;

	if (!changelink) {
		if (conf->flags & VXLAN_F_GPE) {
		/* For now, allow GPE only together with COLLECT_METADATA.
		 * This can be relaxed later; in such case, the other side
		 * of the PtP link will have to be provided.
			/* For now, allow GPE only together with
			 * COLLECT_METADATA. This can be relaxed later; in such
			 * case, the other side of the PtP link will have to be
			 * provided.
			 */
			if ((conf->flags & ~VXLAN_F_ALLOWED_GPE) ||
			    !(conf->flags & VXLAN_F_COLLECT_METADATA)) {
				pr_info("unsupported combination of extensions\n");
				return -EINVAL;
			}

			vxlan_raw_setup(dev);
		} else {
			vxlan_ether_setup(dev);
@@ -2865,8 +2866,8 @@ static int vxlan_dev_configure(struct net *src_net, struct net_device *dev,
		/* MTU range: 68 - 65535 */
		dev->min_mtu = ETH_MIN_MTU;
		dev->max_mtu = ETH_MAX_MTU;

		vxlan->net = src_net;
	}

	dst->remote_vni = conf->vni;

@@ -2889,12 +2890,14 @@ static int vxlan_dev_configure(struct net *src_net, struct net_device *dev,
		return -EINVAL;
	}

	if (conf->remote_ifindex) {
	if (conf->remote_ifindex &&
	    conf->remote_ifindex != vxlan->cfg.remote_ifindex) {
		lowerdev = __dev_get_by_index(src_net, conf->remote_ifindex);
		dst->remote_ifindex = conf->remote_ifindex;

		if (!lowerdev) {
			pr_info("ifindex %d does not exist\n", dst->remote_ifindex);
			pr_info("ifindex %d does not exist\n",
				dst->remote_ifindex);
			return -ENODEV;
		}

@@ -2913,7 +2916,8 @@ static int vxlan_dev_configure(struct net *src_net, struct net_device *dev,
				   (use_ipv6 ? VXLAN6_HEADROOM : VXLAN_HEADROOM);

		needed_headroom = lowerdev->hard_header_len;
	} else if (vxlan_addr_multicast(&dst->remote_ip)) {
	} else if (!conf->remote_ifindex &&
		   vxlan_addr_multicast(&dst->remote_ip)) {
		pr_info("multicast destination requires interface to be specified\n");
		return -EINVAL;
	}
@@ -2953,6 +2957,9 @@ static int vxlan_dev_configure(struct net *src_net, struct net_device *dev,
	if (!vxlan->cfg.age_interval)
		vxlan->cfg.age_interval = FDB_AGE_DEFAULT;

	if (changelink)
		return 0;

	list_for_each_entry(tmp, &vn->vxlan_list, next) {
		if (tmp->cfg.vni == conf->vni &&
		    (tmp->default_dst.remote_ip.sa.sa_family == AF_INET6 ||
@@ -2965,147 +2972,296 @@ static int vxlan_dev_configure(struct net *src_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)) {
		err = vxlan_fdb_create(vxlan, all_zeros_mac,
				       &vxlan->default_dst.remote_ip,
				       NUD_REACHABLE|NUD_PERMANENT,
				       NLM_F_EXCL|NLM_F_CREATE,
				       vxlan->cfg.dst_port,
				       vxlan->default_dst.remote_vni,
				       vxlan->default_dst.remote_vni,
				       vxlan->default_dst.remote_ifindex,
				       NTF_SELF);
		if (err)
			return err;
	}

	err = register_netdevice(dev);
	if (err) {
		vxlan_fdb_delete_default(vxlan, vxlan->cfg.vni);
		return err;
	}

	list_add(&vxlan->next, &vn->vxlan_list);

	return 0;
}

static int vxlan_newlink(struct net *src_net, struct net_device *dev,
			 struct nlattr *tb[], struct nlattr *data[])
static int vxlan_nl2conf(struct nlattr *tb[], struct nlattr *data[],
			 struct net_device *dev, struct vxlan_config *conf,
			 bool changelink)
{
	struct vxlan_config conf;
	struct vxlan_dev *vxlan = netdev_priv(dev);

	memset(conf, 0, sizeof(*conf));

	/* if changelink operation, start with old existing cfg */
	if (changelink)
		memcpy(conf, &vxlan->cfg, sizeof(*conf));

	memset(&conf, 0, sizeof(conf));
	if (data[IFLA_VXLAN_ID]) {
		__be32 vni = cpu_to_be32(nla_get_u32(data[IFLA_VXLAN_ID]));

	if (data[IFLA_VXLAN_ID])
		conf.vni = cpu_to_be32(nla_get_u32(data[IFLA_VXLAN_ID]));
		if (changelink && (vni != conf->vni))
			return -EOPNOTSUPP;
		conf->vni = cpu_to_be32(nla_get_u32(data[IFLA_VXLAN_ID]));
	}

	if (data[IFLA_VXLAN_GROUP]) {
		conf.remote_ip.sin.sin_addr.s_addr = nla_get_in_addr(data[IFLA_VXLAN_GROUP]);
		conf->remote_ip.sin.sin_addr.s_addr = nla_get_in_addr(data[IFLA_VXLAN_GROUP]);
	} else if (data[IFLA_VXLAN_GROUP6]) {
		if (!IS_ENABLED(CONFIG_IPV6))
			return -EPFNOSUPPORT;

		conf.remote_ip.sin6.sin6_addr = nla_get_in6_addr(data[IFLA_VXLAN_GROUP6]);
		conf.remote_ip.sa.sa_family = AF_INET6;
		conf->remote_ip.sin6.sin6_addr = nla_get_in6_addr(data[IFLA_VXLAN_GROUP6]);
		conf->remote_ip.sa.sa_family = AF_INET6;
	}

	if (data[IFLA_VXLAN_LOCAL]) {
		conf.saddr.sin.sin_addr.s_addr = nla_get_in_addr(data[IFLA_VXLAN_LOCAL]);
		conf.saddr.sa.sa_family = AF_INET;
		conf->saddr.sin.sin_addr.s_addr = nla_get_in_addr(data[IFLA_VXLAN_LOCAL]);
		conf->saddr.sa.sa_family = AF_INET;
	} else if (data[IFLA_VXLAN_LOCAL6]) {
		if (!IS_ENABLED(CONFIG_IPV6))
			return -EPFNOSUPPORT;

		/* TODO: respect scope id */
		conf.saddr.sin6.sin6_addr = nla_get_in6_addr(data[IFLA_VXLAN_LOCAL6]);
		conf.saddr.sa.sa_family = AF_INET6;
		conf->saddr.sin6.sin6_addr = nla_get_in6_addr(data[IFLA_VXLAN_LOCAL6]);
		conf->saddr.sa.sa_family = AF_INET6;
	}

	if (data[IFLA_VXLAN_LINK])
		conf.remote_ifindex = nla_get_u32(data[IFLA_VXLAN_LINK]);
		conf->remote_ifindex = nla_get_u32(data[IFLA_VXLAN_LINK]);

	if (data[IFLA_VXLAN_TOS])
		conf.tos  = nla_get_u8(data[IFLA_VXLAN_TOS]);
		conf->tos  = nla_get_u8(data[IFLA_VXLAN_TOS]);

	if (data[IFLA_VXLAN_TTL])
		conf.ttl = nla_get_u8(data[IFLA_VXLAN_TTL]);
		conf->ttl = nla_get_u8(data[IFLA_VXLAN_TTL]);

	if (data[IFLA_VXLAN_LABEL])
		conf.label = nla_get_be32(data[IFLA_VXLAN_LABEL]) &
		conf->label = nla_get_be32(data[IFLA_VXLAN_LABEL]) &
			     IPV6_FLOWLABEL_MASK;

	if (!data[IFLA_VXLAN_LEARNING] || nla_get_u8(data[IFLA_VXLAN_LEARNING]))
		conf.flags |= VXLAN_F_LEARN;
	if (data[IFLA_VXLAN_LEARNING]) {
		if (nla_get_u8(data[IFLA_VXLAN_LEARNING])) {
			conf->flags |= VXLAN_F_LEARN;
		} else {
			conf->flags &= ~VXLAN_F_LEARN;
			vxlan->flags &= ~VXLAN_F_LEARN;
		}
	} else if (!changelink) {
		/* default to learn on a new device */
		conf->flags |= VXLAN_F_LEARN;
	}

	if (data[IFLA_VXLAN_AGEING])
		conf.age_interval = nla_get_u32(data[IFLA_VXLAN_AGEING]);
	if (data[IFLA_VXLAN_AGEING]) {
		if (changelink)
			return -EOPNOTSUPP;
		conf->age_interval = nla_get_u32(data[IFLA_VXLAN_AGEING]);
	}

	if (data[IFLA_VXLAN_PROXY] && nla_get_u8(data[IFLA_VXLAN_PROXY]))
		conf.flags |= VXLAN_F_PROXY;
	if (data[IFLA_VXLAN_PROXY]) {
		if (changelink)
			return -EOPNOTSUPP;
		if (nla_get_u8(data[IFLA_VXLAN_PROXY]))
			conf->flags |= VXLAN_F_PROXY;
	}

	if (data[IFLA_VXLAN_RSC] && nla_get_u8(data[IFLA_VXLAN_RSC]))
		conf.flags |= VXLAN_F_RSC;
	if (data[IFLA_VXLAN_RSC]) {
		if (changelink)
			return -EOPNOTSUPP;
		if (nla_get_u8(data[IFLA_VXLAN_RSC]))
			conf->flags |= VXLAN_F_RSC;
	}

	if (data[IFLA_VXLAN_L2MISS] && nla_get_u8(data[IFLA_VXLAN_L2MISS]))
		conf.flags |= VXLAN_F_L2MISS;
	if (data[IFLA_VXLAN_L2MISS]) {
		if (changelink)
			return -EOPNOTSUPP;
		if (nla_get_u8(data[IFLA_VXLAN_L2MISS]))
			conf->flags |= VXLAN_F_L2MISS;
	}

	if (data[IFLA_VXLAN_L3MISS] && nla_get_u8(data[IFLA_VXLAN_L3MISS]))
		conf.flags |= VXLAN_F_L3MISS;
	if (data[IFLA_VXLAN_L3MISS]) {
		if (changelink)
			return -EOPNOTSUPP;
		if (nla_get_u8(data[IFLA_VXLAN_L3MISS]))
			conf->flags |= VXLAN_F_L3MISS;
	}

	if (data[IFLA_VXLAN_LIMIT])
		conf.addrmax = nla_get_u32(data[IFLA_VXLAN_LIMIT]);
	if (data[IFLA_VXLAN_LIMIT]) {
		if (changelink)
			return -EOPNOTSUPP;
		conf->addrmax = nla_get_u32(data[IFLA_VXLAN_LIMIT]);
	}

	if (data[IFLA_VXLAN_COLLECT_METADATA] &&
	    nla_get_u8(data[IFLA_VXLAN_COLLECT_METADATA]))
		conf.flags |= VXLAN_F_COLLECT_METADATA;
	if (data[IFLA_VXLAN_COLLECT_METADATA]) {
		if (changelink)
			return -EOPNOTSUPP;
		if (nla_get_u8(data[IFLA_VXLAN_COLLECT_METADATA]))
			conf->flags |= VXLAN_F_COLLECT_METADATA;
	}

	if (data[IFLA_VXLAN_PORT_RANGE]) {
		if (!changelink) {
			const struct ifla_vxlan_port_range *p
				= nla_data(data[IFLA_VXLAN_PORT_RANGE]);
		conf.port_min = ntohs(p->low);
		conf.port_max = ntohs(p->high);
			conf->port_min = ntohs(p->low);
			conf->port_max = ntohs(p->high);
		} else {
			return -EOPNOTSUPP;
		}
	}

	if (data[IFLA_VXLAN_PORT]) {
		if (changelink)
			return -EOPNOTSUPP;
		conf->dst_port = nla_get_be16(data[IFLA_VXLAN_PORT]);
	}

	if (data[IFLA_VXLAN_UDP_CSUM]) {
		if (changelink)
			return -EOPNOTSUPP;
		if (!nla_get_u8(data[IFLA_VXLAN_UDP_CSUM]))
			conf->flags |= VXLAN_F_UDP_ZERO_CSUM_TX;
	}

	if (data[IFLA_VXLAN_UDP_ZERO_CSUM6_TX]) {
		if (changelink)
			return -EOPNOTSUPP;
		if (nla_get_u8(data[IFLA_VXLAN_UDP_ZERO_CSUM6_TX]))
			conf->flags |= VXLAN_F_UDP_ZERO_CSUM6_TX;
	}

	if (data[IFLA_VXLAN_UDP_ZERO_CSUM6_RX]) {
		if (changelink)
			return -EOPNOTSUPP;
		if (nla_get_u8(data[IFLA_VXLAN_UDP_ZERO_CSUM6_RX]))
			conf->flags |= VXLAN_F_UDP_ZERO_CSUM6_RX;
	}

	if (data[IFLA_VXLAN_REMCSUM_TX]) {
		if (changelink)
			return -EOPNOTSUPP;
		if (nla_get_u8(data[IFLA_VXLAN_REMCSUM_TX]))
			conf->flags |= VXLAN_F_REMCSUM_TX;
	}

	if (data[IFLA_VXLAN_REMCSUM_RX]) {
		if (changelink)
			return -EOPNOTSUPP;
		if (nla_get_u8(data[IFLA_VXLAN_REMCSUM_RX]))
			conf->flags |= VXLAN_F_REMCSUM_RX;
	}

	if (data[IFLA_VXLAN_GBP]) {
		if (changelink)
			return -EOPNOTSUPP;
		conf->flags |= VXLAN_F_GBP;
	}

	if (data[IFLA_VXLAN_GPE]) {
		if (changelink)
			return -EOPNOTSUPP;
		conf->flags |= VXLAN_F_GPE;
	}

	if (data[IFLA_VXLAN_REMCSUM_NOPARTIAL]) {
		if (changelink)
			return -EOPNOTSUPP;
		conf->flags |= VXLAN_F_REMCSUM_NOPARTIAL;
	}

	if (tb[IFLA_MTU]) {
		if (changelink)
			return -EOPNOTSUPP;
		conf->mtu = nla_get_u32(tb[IFLA_MTU]);
	}

	return 0;
}

	if (data[IFLA_VXLAN_PORT])
		conf.dst_port = nla_get_be16(data[IFLA_VXLAN_PORT]);
static int vxlan_newlink(struct net *src_net, struct net_device *dev,
			 struct nlattr *tb[], struct nlattr *data[])
{
	struct vxlan_net *vn = net_generic(src_net, vxlan_net_id);
	struct vxlan_dev *vxlan = netdev_priv(dev);
	struct vxlan_config conf;
	int err;

	err = vxlan_nl2conf(tb, data, dev, &conf, false);
	if (err)
		return err;

	err = vxlan_dev_configure(src_net, dev, &conf, false);
	if (err)
		return err;

	dev->ethtool_ops = &vxlan_ethtool_ops;

	/* create an fdb entry for a valid default destination */
	if (!vxlan_addr_any(&vxlan->default_dst.remote_ip)) {
		err = vxlan_fdb_create(vxlan, all_zeros_mac,
				       &vxlan->default_dst.remote_ip,
				       NUD_REACHABLE | NUD_PERMANENT,
				       NLM_F_EXCL | NLM_F_CREATE,
				       vxlan->cfg.dst_port,
				       vxlan->default_dst.remote_vni,
				       vxlan->default_dst.remote_vni,
				       vxlan->default_dst.remote_ifindex,
				       NTF_SELF);
		if (err)
			return err;
	}

	if (data[IFLA_VXLAN_UDP_CSUM] &&
	    !nla_get_u8(data[IFLA_VXLAN_UDP_CSUM]))
		conf.flags |= VXLAN_F_UDP_ZERO_CSUM_TX;
	err = register_netdevice(dev);
	if (err) {
		vxlan_fdb_delete_default(vxlan, vxlan->default_dst.remote_vni);
		return err;
	}

	if (data[IFLA_VXLAN_UDP_ZERO_CSUM6_TX] &&
	    nla_get_u8(data[IFLA_VXLAN_UDP_ZERO_CSUM6_TX]))
		conf.flags |= VXLAN_F_UDP_ZERO_CSUM6_TX;
	list_add(&vxlan->next, &vn->vxlan_list);

	if (data[IFLA_VXLAN_UDP_ZERO_CSUM6_RX] &&
	    nla_get_u8(data[IFLA_VXLAN_UDP_ZERO_CSUM6_RX]))
		conf.flags |= VXLAN_F_UDP_ZERO_CSUM6_RX;
	return 0;
}

	if (data[IFLA_VXLAN_REMCSUM_TX] &&
	    nla_get_u8(data[IFLA_VXLAN_REMCSUM_TX]))
		conf.flags |= VXLAN_F_REMCSUM_TX;
static int vxlan_changelink(struct net_device *dev, struct nlattr *tb[],
			    struct nlattr *data[])
{
	struct vxlan_dev *vxlan = netdev_priv(dev);
	struct vxlan_rdst *dst = &vxlan->default_dst;
	struct vxlan_rdst old_dst;
	struct vxlan_config conf;
	int err;

	if (data[IFLA_VXLAN_REMCSUM_RX] &&
	    nla_get_u8(data[IFLA_VXLAN_REMCSUM_RX]))
		conf.flags |= VXLAN_F_REMCSUM_RX;
	err = vxlan_nl2conf(tb, data,
			    dev, &conf, true);
	if (err)
		return err;

	if (data[IFLA_VXLAN_GBP])
		conf.flags |= VXLAN_F_GBP;
	memcpy(&old_dst, dst, sizeof(struct vxlan_rdst));

	if (data[IFLA_VXLAN_GPE])
		conf.flags |= VXLAN_F_GPE;
	err = vxlan_dev_configure(vxlan->net, dev, &conf, true);
	if (err)
		return err;

	if (data[IFLA_VXLAN_REMCSUM_NOPARTIAL])
		conf.flags |= VXLAN_F_REMCSUM_NOPARTIAL;
	/* handle default dst entry */
	if (!vxlan_addr_equal(&dst->remote_ip, &old_dst.remote_ip)) {
		spin_lock_bh(&vxlan->hash_lock);
		if (!vxlan_addr_any(&old_dst.remote_ip))
			__vxlan_fdb_delete(vxlan, all_zeros_mac,
					   old_dst.remote_ip,
					   vxlan->cfg.dst_port,
					   old_dst.remote_vni,
					   old_dst.remote_vni,
					   old_dst.remote_ifindex, 0);

	if (tb[IFLA_MTU])
		conf.mtu = nla_get_u32(tb[IFLA_MTU]);
		if (!vxlan_addr_any(&dst->remote_ip)) {
			err = vxlan_fdb_create(vxlan, all_zeros_mac,
					       &dst->remote_ip,
					       NUD_REACHABLE | NUD_PERMANENT,
					       NLM_F_CREATE | NLM_F_APPEND,
					       vxlan->cfg.dst_port,
					       dst->remote_vni,
					       dst->remote_vni,
					       dst->remote_ifindex,
					       NTF_SELF);
			if (err) {
				spin_unlock_bh(&vxlan->hash_lock);
				return err;
			}
		}
		spin_unlock_bh(&vxlan->hash_lock);
	}

	return vxlan_dev_configure(src_net, dev, &conf);
	return 0;
}

static void vxlan_dellink(struct net_device *dev, struct list_head *head)
@@ -3261,6 +3417,7 @@ static struct rtnl_link_ops vxlan_link_ops __read_mostly = {
	.setup		= vxlan_setup,
	.validate	= vxlan_validate,
	.newlink	= vxlan_newlink,
	.changelink	= vxlan_changelink,
	.dellink	= vxlan_dellink,
	.get_size	= vxlan_get_size,
	.fill_info	= vxlan_fill_info,
@@ -3282,7 +3439,7 @@ struct net_device *vxlan_dev_create(struct net *net, const char *name,
	if (IS_ERR(dev))
		return dev;

	err = vxlan_dev_configure(net, dev, conf);
	err = vxlan_dev_configure(net, dev, conf, false);
	if (err < 0) {
		free_netdev(dev);
		return ERR_PTR(err);