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

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
...@@ -2835,28 +2835,29 @@ static int vxlan_sock_add(struct vxlan_dev *vxlan) ...@@ -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, 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_net *vn = net_generic(src_net, vxlan_net_id);
struct vxlan_dev *vxlan = netdev_priv(dev), *tmp; struct vxlan_dev *vxlan = netdev_priv(dev), *tmp;
struct vxlan_rdst *dst = &vxlan->default_dst; struct vxlan_rdst *dst = &vxlan->default_dst;
unsigned short needed_headroom = ETH_HLEN; unsigned short needed_headroom = ETH_HLEN;
int err;
bool use_ipv6 = false; bool use_ipv6 = false;
__be16 default_port = vxlan->cfg.dst_port; __be16 default_port = vxlan->cfg.dst_port;
struct net_device *lowerdev = NULL; struct net_device *lowerdev = NULL;
if (!changelink) {
if (conf->flags & VXLAN_F_GPE) { if (conf->flags & VXLAN_F_GPE) {
/* For now, allow GPE only together with COLLECT_METADATA. /* For now, allow GPE only together with
* This can be relaxed later; in such case, the other side * COLLECT_METADATA. This can be relaxed later; in such
* of the PtP link will have to be provided. * case, the other side of the PtP link will have to be
* provided.
*/ */
if ((conf->flags & ~VXLAN_F_ALLOWED_GPE) || if ((conf->flags & ~VXLAN_F_ALLOWED_GPE) ||
!(conf->flags & VXLAN_F_COLLECT_METADATA)) { !(conf->flags & VXLAN_F_COLLECT_METADATA)) {
pr_info("unsupported combination of extensions\n"); pr_info("unsupported combination of extensions\n");
return -EINVAL; return -EINVAL;
} }
vxlan_raw_setup(dev); vxlan_raw_setup(dev);
} else { } else {
vxlan_ether_setup(dev); vxlan_ether_setup(dev);
...@@ -2865,8 +2866,8 @@ static int vxlan_dev_configure(struct net *src_net, struct net_device *dev, ...@@ -2865,8 +2866,8 @@ static int vxlan_dev_configure(struct net *src_net, struct net_device *dev,
/* MTU range: 68 - 65535 */ /* MTU range: 68 - 65535 */
dev->min_mtu = ETH_MIN_MTU; dev->min_mtu = ETH_MIN_MTU;
dev->max_mtu = ETH_MAX_MTU; dev->max_mtu = ETH_MAX_MTU;
vxlan->net = src_net; vxlan->net = src_net;
}
dst->remote_vni = conf->vni; dst->remote_vni = conf->vni;
...@@ -2889,12 +2890,14 @@ static int vxlan_dev_configure(struct net *src_net, struct net_device *dev, ...@@ -2889,12 +2890,14 @@ static int vxlan_dev_configure(struct net *src_net, struct net_device *dev,
return -EINVAL; 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); lowerdev = __dev_get_by_index(src_net, conf->remote_ifindex);
dst->remote_ifindex = conf->remote_ifindex; dst->remote_ifindex = conf->remote_ifindex;
if (!lowerdev) { 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; return -ENODEV;
} }
...@@ -2913,7 +2916,8 @@ static int vxlan_dev_configure(struct net *src_net, struct net_device *dev, ...@@ -2913,7 +2916,8 @@ static int vxlan_dev_configure(struct net *src_net, struct net_device *dev,
(use_ipv6 ? VXLAN6_HEADROOM : VXLAN_HEADROOM); (use_ipv6 ? VXLAN6_HEADROOM : VXLAN_HEADROOM);
needed_headroom = lowerdev->hard_header_len; 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"); pr_info("multicast destination requires interface to be specified\n");
return -EINVAL; return -EINVAL;
} }
...@@ -2953,6 +2957,9 @@ static int vxlan_dev_configure(struct net *src_net, struct net_device *dev, ...@@ -2953,6 +2957,9 @@ static int vxlan_dev_configure(struct net *src_net, struct net_device *dev,
if (!vxlan->cfg.age_interval) if (!vxlan->cfg.age_interval)
vxlan->cfg.age_interval = FDB_AGE_DEFAULT; vxlan->cfg.age_interval = FDB_AGE_DEFAULT;
if (changelink)
return 0;
list_for_each_entry(tmp, &vn->vxlan_list, next) { list_for_each_entry(tmp, &vn->vxlan_list, next) {
if (tmp->cfg.vni == conf->vni && if (tmp->cfg.vni == conf->vni &&
(tmp->default_dst.remote_ip.sa.sa_family == AF_INET6 || (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, ...@@ -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; return 0;
} }
static int vxlan_newlink(struct net *src_net, struct net_device *dev, static int vxlan_nl2conf(struct nlattr *tb[], struct nlattr *data[],
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]) if (changelink && (vni != conf->vni))
conf.vni = cpu_to_be32(nla_get_u32(data[IFLA_VXLAN_ID])); return -EOPNOTSUPP;
conf->vni = cpu_to_be32(nla_get_u32(data[IFLA_VXLAN_ID]));
}
if (data[IFLA_VXLAN_GROUP]) { 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]) { } else if (data[IFLA_VXLAN_GROUP6]) {
if (!IS_ENABLED(CONFIG_IPV6)) if (!IS_ENABLED(CONFIG_IPV6))
return -EPFNOSUPPORT; return -EPFNOSUPPORT;
conf.remote_ip.sin6.sin6_addr = nla_get_in6_addr(data[IFLA_VXLAN_GROUP6]); 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.sa.sa_family = AF_INET6;
} }
if (data[IFLA_VXLAN_LOCAL]) { if (data[IFLA_VXLAN_LOCAL]) {
conf.saddr.sin.sin_addr.s_addr = nla_get_in_addr(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.sa.sa_family = AF_INET;
} else if (data[IFLA_VXLAN_LOCAL6]) { } else if (data[IFLA_VXLAN_LOCAL6]) {
if (!IS_ENABLED(CONFIG_IPV6)) if (!IS_ENABLED(CONFIG_IPV6))
return -EPFNOSUPPORT; return -EPFNOSUPPORT;
/* TODO: respect scope id */ /* TODO: respect scope id */
conf.saddr.sin6.sin6_addr = nla_get_in6_addr(data[IFLA_VXLAN_LOCAL6]); conf->saddr.sin6.sin6_addr = nla_get_in6_addr(data[IFLA_VXLAN_LOCAL6]);
conf.saddr.sa.sa_family = AF_INET6; conf->saddr.sa.sa_family = AF_INET6;
} }
if (data[IFLA_VXLAN_LINK]) 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]) 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]) 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]) 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; IPV6_FLOWLABEL_MASK;
if (!data[IFLA_VXLAN_LEARNING] || nla_get_u8(data[IFLA_VXLAN_LEARNING])) if (data[IFLA_VXLAN_LEARNING]) {
conf.flags |= VXLAN_F_LEARN; 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]) if (data[IFLA_VXLAN_AGEING]) {
conf.age_interval = nla_get_u32(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])) if (data[IFLA_VXLAN_PROXY]) {
conf.flags |= VXLAN_F_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])) if (data[IFLA_VXLAN_RSC]) {
conf.flags |= VXLAN_F_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])) if (data[IFLA_VXLAN_L2MISS]) {
conf.flags |= VXLAN_F_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])) if (data[IFLA_VXLAN_L3MISS]) {
conf.flags |= VXLAN_F_L3MISS; if (changelink)
return -EOPNOTSUPP;
if (nla_get_u8(data[IFLA_VXLAN_L3MISS]))
conf->flags |= VXLAN_F_L3MISS;
}
if (data[IFLA_VXLAN_LIMIT]) if (data[IFLA_VXLAN_LIMIT]) {
conf.addrmax = nla_get_u32(data[IFLA_VXLAN_LIMIT]); if (changelink)
return -EOPNOTSUPP;
conf->addrmax = nla_get_u32(data[IFLA_VXLAN_LIMIT]);
}
if (data[IFLA_VXLAN_COLLECT_METADATA] && if (data[IFLA_VXLAN_COLLECT_METADATA]) {
nla_get_u8(data[IFLA_VXLAN_COLLECT_METADATA])) if (changelink)
conf.flags |= VXLAN_F_COLLECT_METADATA; return -EOPNOTSUPP;
if (nla_get_u8(data[IFLA_VXLAN_COLLECT_METADATA]))
conf->flags |= VXLAN_F_COLLECT_METADATA;
}
if (data[IFLA_VXLAN_PORT_RANGE]) { if (data[IFLA_VXLAN_PORT_RANGE]) {
if (!changelink) {
const struct ifla_vxlan_port_range *p const struct ifla_vxlan_port_range *p
= nla_data(data[IFLA_VXLAN_PORT_RANGE]); = nla_data(data[IFLA_VXLAN_PORT_RANGE]);
conf.port_min = ntohs(p->low); conf->port_min = ntohs(p->low);
conf.port_max = ntohs(p->high); 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_PORT]) if (data[IFLA_VXLAN_REMCSUM_RX]) {
conf.dst_port = nla_get_be16(data[IFLA_VXLAN_PORT]); 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;
}
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] && err = register_netdevice(dev);
!nla_get_u8(data[IFLA_VXLAN_UDP_CSUM])) if (err) {
conf.flags |= VXLAN_F_UDP_ZERO_CSUM_TX; vxlan_fdb_delete_default(vxlan, vxlan->default_dst.remote_vni);
return err;
}
if (data[IFLA_VXLAN_UDP_ZERO_CSUM6_TX] && list_add(&vxlan->next, &vn->vxlan_list);
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] && return 0;
nla_get_u8(data[IFLA_VXLAN_UDP_ZERO_CSUM6_RX])) }
conf.flags |= VXLAN_F_UDP_ZERO_CSUM6_RX;
if (data[IFLA_VXLAN_REMCSUM_TX] && static int vxlan_changelink(struct net_device *dev, struct nlattr *tb[],
nla_get_u8(data[IFLA_VXLAN_REMCSUM_TX])) struct nlattr *data[])
conf.flags |= VXLAN_F_REMCSUM_TX; {
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] && err = vxlan_nl2conf(tb, data,
nla_get_u8(data[IFLA_VXLAN_REMCSUM_RX])) dev, &conf, true);
conf.flags |= VXLAN_F_REMCSUM_RX; if (err)
return err;
if (data[IFLA_VXLAN_GBP]) memcpy(&old_dst, dst, sizeof(struct vxlan_rdst));
conf.flags |= VXLAN_F_GBP;
if (data[IFLA_VXLAN_GPE]) err = vxlan_dev_configure(vxlan->net, dev, &conf, true);
conf.flags |= VXLAN_F_GPE; if (err)
return err;
if (data[IFLA_VXLAN_REMCSUM_NOPARTIAL]) /* handle default dst entry */
conf.flags |= VXLAN_F_REMCSUM_NOPARTIAL; 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]) if (!vxlan_addr_any(&dst->remote_ip)) {
conf.mtu = nla_get_u32(tb[IFLA_MTU]); 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) 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 = { ...@@ -3261,6 +3417,7 @@ static struct rtnl_link_ops vxlan_link_ops __read_mostly = {
.setup = vxlan_setup, .setup = vxlan_setup,
.validate = vxlan_validate, .validate = vxlan_validate,
.newlink = vxlan_newlink, .newlink = vxlan_newlink,
.changelink = vxlan_changelink,
.dellink = vxlan_dellink, .dellink = vxlan_dellink,
.get_size = vxlan_get_size, .get_size = vxlan_get_size,
.fill_info = vxlan_fill_info, .fill_info = vxlan_fill_info,
...@@ -3282,7 +3439,7 @@ struct net_device *vxlan_dev_create(struct net *net, const char *name, ...@@ -3282,7 +3439,7 @@ struct net_device *vxlan_dev_create(struct net *net, const char *name,
if (IS_ERR(dev)) if (IS_ERR(dev))
return dev; return dev;
err = vxlan_dev_configure(net, dev, conf); err = vxlan_dev_configure(net, dev, conf, false);
if (err < 0) { if (err < 0) {
free_netdev(dev); free_netdev(dev);
return ERR_PTR(err); return ERR_PTR(err);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment