Index: linux-4.19.118/drivers/net/vxlan.c =================================================================== --- linux-4.19.118.orig/drivers/net/vxlan.c +++ linux-4.19.118/drivers/net/vxlan.c @@ -3284,11 +3284,17 @@ static int __vxlan_dev_create(struct net if (err) goto errout; + err = vxlan_sysfs_dev_add(dev); + if (err) + goto errout; + /* notify default fdb entry */ if (f) vxlan_fdb_notify(vxlan, f, first_remote_rtnl(f), RTM_NEWNEIGH); list_add(&vxlan->next, &vn->vxlan_list); + + return 0; errout: @@ -3593,6 +3599,8 @@ static void vxlan_dellink(struct net_dev list_del(&vxlan->next); unregister_netdevice_queue(dev, head); + + vxlan_sysfs_dev_del(dev); } static size_t vxlan_get_size(const struct net_device *dev) @@ -3735,6 +3743,7 @@ static struct rtnl_link_ops vxlan_link_o .setup = vxlan_setup, .validate = vxlan_validate, .newlink = vxlan_newlink, + .changelink = vxlan_changelink, .dellink = vxlan_dellink, .get_size = vxlan_get_size, @@ -3812,6 +3821,10 @@ static int vxlan_netdevice_event(struct } else if (event == NETDEV_UDP_TUNNEL_PUSH_INFO || event == NETDEV_UDP_TUNNEL_DROP_INFO) { vxlan_offload_rx_ports(dev, event == NETDEV_UDP_TUNNEL_PUSH_INFO); + } else if (event == NETDEV_CHANGENAME) { + int err; + if ((err = vxlan_sysfs_dev_ren(dev))) + return notifier_from_errno(err); } return NOTIFY_DONE; @@ -3896,6 +3909,10 @@ static int __init vxlan_init_module(void if (rc) goto out3; + rc = vxlan_sysfs_mod_add(); + if (rc) + goto out3; + return 0; out3: unregister_netdevice_notifier(&vxlan_notifier_block); @@ -3912,7 +3929,340 @@ static void __exit vxlan_cleanup_module( unregister_netdevice_notifier(&vxlan_notifier_block); unregister_pernet_subsys(&vxlan_net_ops); /* rcu_barrier() is called by netns */ + vxlan_sysfs_mod_del(); +} + +/* sysfs code lifted/inspired from bridge */ +#define to_vxlan_dev(cd) ((struct vxlan_dev *)netdev_priv(to_net_dev(cd))) + +#define VXLAN_ATTR_FLAG(_name, _mask) \ +static ssize_t _name##_show(struct device *d, \ + struct device_attribute *attr, char *buf) \ +{ \ + struct vxlan_dev *p = to_vxlan_dev(d); \ + return sprintf(buf, "%d\n", !!(p->cfg.flags & _mask)); \ +} \ +static ssize_t _name##_store(struct device *d, struct \ + device_attribute *attr, const char *buf, size_t len) \ +{ \ + struct vxlan_dev *p = to_vxlan_dev(d); \ + unsigned long v; \ + if (kstrtoul(buf, 0, &v)) \ + return -EINVAL; \ + return (!store_flag(p, v, _mask)) ? len : -EINVAL; \ +} \ +static DEVICE_ATTR_RW(_name); + +#define VXLAN_ATTR_FLAG_RO(_name, _mask) \ +static ssize_t _name##_show(struct device *d, \ + struct device_attribute *attr, char *buf) \ +{ \ + struct vxlan_dev *p = to_vxlan_dev(d); \ + return sprintf(buf, "%d\n", !!(p->cfg.flags & _mask)); \ +} \ +static DEVICE_ATTR_RO(_name); + +static int store_flag(struct vxlan_dev *p, unsigned long v, + unsigned long mask) +{ + unsigned long flags; + + flags = p->cfg.flags; + + if (v) + flags |= mask; + else + flags &= ~mask; + + if (flags != p->cfg.flags) + p->cfg.flags = flags; + + return 1; +} + +VXLAN_ATTR_FLAG(learn, VXLAN_F_LEARN); +VXLAN_ATTR_FLAG(proxy, VXLAN_F_PROXY); +VXLAN_ATTR_FLAG_RO(rsc, VXLAN_F_RSC); +VXLAN_ATTR_FLAG(l2miss, VXLAN_F_L2MISS); +VXLAN_ATTR_FLAG(l3miss, VXLAN_F_L3MISS); +VXLAN_ATTR_FLAG_RO(ipv6, VXLAN_F_IPV6); +VXLAN_ATTR_FLAG_RO(udp_zero_csum_tx, VXLAN_F_UDP_ZERO_CSUM_TX); +VXLAN_ATTR_FLAG_RO(udp_zero_csum6_tx, VXLAN_F_UDP_ZERO_CSUM6_TX); +VXLAN_ATTR_FLAG_RO(udp_zero_csum6_rx, VXLAN_F_UDP_ZERO_CSUM6_RX); +VXLAN_ATTR_FLAG_RO(remcsum_tx, VXLAN_F_REMCSUM_TX); +VXLAN_ATTR_FLAG_RO(remcsum_rx, VXLAN_F_REMCSUM_RX); +VXLAN_ATTR_FLAG_RO(gbp, VXLAN_F_GBP); +VXLAN_ATTR_FLAG_RO(remcsum_nopartial, VXLAN_F_REMCSUM_NOPARTIAL); +VXLAN_ATTR_FLAG(collect_metadata, VXLAN_F_COLLECT_METADATA); +VXLAN_ATTR_FLAG(gpe, VXLAN_F_GPE); +VXLAN_ATTR_FLAG_RO(ipv6_linklocal, VXLAN_F_IPV6_LINKLOCAL); +VXLAN_ATTR_FLAG(ttl_inherit, VXLAN_F_TTL_INHERIT); + +static struct attribute *vxlan_attrs[] = { + &dev_attr_learn.attr, + &dev_attr_proxy.attr, + &dev_attr_rsc.attr, + &dev_attr_l2miss.attr, + &dev_attr_l3miss.attr, + &dev_attr_ipv6.attr, + &dev_attr_udp_zero_csum_tx.attr, + &dev_attr_udp_zero_csum6_tx.attr, + &dev_attr_udp_zero_csum6_rx.attr, + &dev_attr_remcsum_tx.attr, + &dev_attr_remcsum_rx.attr, + &dev_attr_gbp.attr, + &dev_attr_remcsum_nopartial.attr, + &dev_attr_collect_metadata.attr, + &dev_attr_gpe.attr, + &dev_attr_ipv6_linklocal.attr, + &dev_attr_ttl_inherit.attr, + NULL +}; + +#if 0 +#define to_vxlan_attr(_at) container_of(_at, struct device_attribute, attr) +#define kobj_to_vxlan(obj) container_of(obj, struct vxlan_dev, kobj) + +static ssize_t vxlan_show(struct kobject *kobj, + struct attribute *attr, char *buf) +{ + struct device_attribute *vxlan_attr = to_vxlan_attr(attr); + struct vxlan_dev *p = kobj_to_vxlan(kobj); + + if (!vxlan_attr->show) + return -EINVAL; + + return vxlan_attr->show(p, buf); +} + +static ssize_t vxlan_store(struct kobject *kobj, + struct attribute *attr, + const char *buf, size_t count) +{ + struct device_attribute *vxlan_attr = to_vxlan_attr(attr); + struct vxlan_dev *p = kobj_to_vxlan(kobj); + ssize_t ret = -EINVAL; + unsigned long val; + char *endp; + + if (!ns_capable(dev_net(p->dev)->user_ns, CAP_NET_ADMIN)) + return -EPERM; + + if (!rtnl_trylock()) + return restart_syscall(); + + if (!p->dev) + goto out_unlock; + + if (vxlan_attr->store) { + val = simple_strtoul(buf, &endp, 0); + if (endp == buf) + goto out_unlock; + ret = vxlan_attr->store(p, val); + } + + if (!ret) + ret = count; +out_unlock: + rtnl_unlock(); + + return ret; +} + +const struct sysfs_ops vxlan_sysfs_ops = { + .show = vxlan_show, + .store = vxlan_store, +}; +#endif + + +static const struct attribute_group vxlan_group = { + .name = SYSFS_VXLAN_ATTR, + .attrs = vxlan_attrs, +}; + +/* + * Fill buffer with forwarding table records in + * the API format. + */ +int vxlan_fdb_fillbuf(struct vxlan_dev *vxlan, void *buf, + unsigned long maxnum, unsigned long skip) +{ + struct __vxlan_fdb_entry *fe = buf; + int num = 0; + unsigned int h; + + memset(buf, 0, maxnum*sizeof(struct __vxlan_fdb_entry)); + + rcu_read_lock(); + for (h = 0; h < FDB_HASH_SIZE; ++h) { + struct vxlan_fdb *f; + + hlist_for_each_entry_rcu(f, &vxlan->fdb_head[h], hlist) { + struct vxlan_rdst *rd; + + if (num >= maxnum) + break; + list_for_each_entry_rcu(rd, &f->remotes, list) { + if (num >= maxnum) + break; + if (skip) { + --skip; + continue; + } + + memcpy(fe->eth_addr, f->eth_addr, ETH_ALEN); + fe->state = f->state; + fe->flags = f->flags; + fe->updated = jiffies_delta_to_clock_t(jiffies - f->updated); + fe->used = jiffies_delta_to_clock_t(jiffies - f->used); + memcpy(fe->remote_addr, &rd->remote_ip, sizeof(fe->remote_addr)); + fe->remote_port_lo = rd->remote_port; + fe->remote_port_hi = rd->remote_port >> 8; + fe->remote_vni = rd->remote_vni; + fe->remote_ifindex = rd->remote_ifindex; + ++fe; + ++num; + } + } + } + rcu_read_unlock(); + return num; +} + +/* + * Export the forwarding information table as a binary file + * The records are struct __vxlan_fdb_entry. + * + * Returns the number of bytes read. + */ +static ssize_t vxlan_fdb_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct device *dev = kobj_to_dev(kobj); + struct vxlan_dev *vxlan = to_vxlan_dev(dev); + int n; + + /* must read whole records */ + if (off % sizeof(struct __vxlan_fdb_entry) != 0) + return -EINVAL; + + n = vxlan_fdb_fillbuf(vxlan, buf, + count / sizeof(struct __vxlan_fdb_entry), + off / sizeof(struct __vxlan_fdb_entry)); + + if (n > 0) + n *= sizeof(struct __vxlan_fdb_entry); + + return n; +} +static struct bin_attribute vxlan_forward = { + .attr = { .name = SYSFS_VXLAN_FDB, + .mode = 0444, }, + .read = vxlan_fdb_read, +}; +/* + * Add entries in sysfs onto the existing network class device + * for the vxlan device. + * Adds a attribute group "vxlan" containing tuning parameters. + * Binary attribute containing the forward table + */ +#ifdef CONFIG_SYSFS +int vxlan_sysfs_dev_add(struct net_device *dev) +{ + struct kobject *vxlanobj = &dev->dev.kobj; + struct vxlan_dev *vxlan = netdev_priv(dev); + int err; + + err = sysfs_create_group(vxlanobj, &vxlan_group); + if (err) { + pr_info("%s: can't create group %s/%s\n", + __func__, dev->name, vxlan_group.name); + goto out1; + } + + err = sysfs_create_bin_file(vxlanobj, &vxlan_forward); + if (err) { + pr_info("%s: can't create attribute file %s/%s\n", + __func__, dev->name, vxlan_forward.attr.name); + goto out2; + } + + err = sysfs_create_link(vxlan_if_kobj, vxlanobj, dev->name); + if (err) { + pr_info("%s: can't create module symlink %s/%s\n", + __func__, SYSFS_VXLAN_MODULE_IF_SUBDIR, dev->name); + goto out3; + } + strlcpy(vxlan->sysfs_name, dev->name, IFNAMSIZ); + + + + return 0; + out3: + sysfs_remove_bin_file(&dev->dev.kobj, &vxlan_forward); + out2: + sysfs_remove_group(&dev->dev.kobj, &vxlan_group); + out1: + return err; + +} + +void vxlan_sysfs_dev_del(struct net_device *dev) +{ + struct kobject *kobj = &dev->dev.kobj; + + sysfs_remove_bin_file(kobj, &vxlan_forward); + sysfs_remove_group(kobj, &vxlan_group); + sysfs_remove_link(vxlan_if_kobj, dev->name); +} + +int vxlan_sysfs_dev_ren(struct net_device *dev) +{ + struct vxlan_dev *vxlan = netdev_priv(dev); + int err; + + /* If a rename fails, the rollback will cause another + * rename call with the existing name. + */ + if (!strncmp(vxlan->sysfs_name, vxlan->dev->name, IFNAMSIZ)) + return 0; + + err = sysfs_rename_link(vxlan_if_kobj, &dev->dev.kobj, + vxlan->sysfs_name, vxlan->dev->name); + if (err) + netdev_notice(dev, "unable to rename link %s to %s", + vxlan->sysfs_name, vxlan->dev->name); + else + strlcpy(vxlan->sysfs_name, vxlan->dev->name, IFNAMSIZ); + + return err; } + +/* module init */ +int vxlan_sysfs_mod_add(void) +{ + + int err = 0; + struct kobject *mod_kobj = &(((struct module *)(THIS_MODULE))->mkobj).kobj; + vxlan_if_kobj = kobject_create_and_add(SYSFS_VXLAN_MODULE_IF_SUBDIR, mod_kobj); + if (!vxlan_if_kobj) { + pr_info("%s: can't add module subdirectory %s\n", + __func__, SYSFS_VXLAN_MODULE_IF_SUBDIR); + err = -ENOMEM; + } + return err; +} +void vxlan_sysfs_mod_del(void) +{ + if (vxlan_if_kobj) + kobject_put(vxlan_if_kobj); + vxlan_if_kobj = NULL; +} +#endif + + module_exit(vxlan_cleanup_module); MODULE_LICENSE("GPL"); Index: linux-4.19.118/include/net/vxlan.h =================================================================== --- linux-4.19.118.orig/include/net/vxlan.h +++ linux-4.19.118/include/net/vxlan.h @@ -242,6 +242,7 @@ struct vxlan_dev { struct gro_cells gro_cells; struct vxlan_config cfg; + char sysfs_name[IFNAMSIZ]; struct hlist_head fdb_head[FDB_HASH_SIZE]; }; @@ -264,6 +265,43 @@ struct vxlan_dev { #define VXLAN_F_IPV6_LINKLOCAL 0x8000 #define VXLAN_F_TTL_INHERIT 0x10000 +#define SYSFS_VXLAN_ATTR "vxlan" +#define SYSFS_VXLAN_FDB "fdbread" +#define SYSFS_VXLAN_MODULE_IF_SUBDIR "if" +#ifdef CONFIG_SYSFS +struct kobject *vxlan_if_kobj = NULL; +int vxlan_sysfs_dev_add(struct net_device *dev); +int vxlan_sysfs_dev_ren(struct net_device *dev); +void vxlan_sysfs_dev_del(struct net_device *dev); +int vxlan_sysfs_mod_add(void); +void vxlan_sysfs_mod_del(void); +#else +static inline int vxlan_sysfs_dev_add(struct net_device *dev) { return 0; } +static inline int vxlan_sysfs_dev_ren(struct net_device *dev) { return 0; } +static inline void vxlan_sysfs_dev_del(struct net_device *dev) {return; } +static inline int vxlan_sysfs_mod_add(void) {return 0;} +static inline void vxlan_sysfs_mod_del(void) {return; } +#endif + +/* API */ +struct __vxlan_fdb_entry { + + __u8 eth_addr[ETH_ALEN+1]; + __u16 state; /*see ndm */ + __u32 vni; + __u16 flags; + __u32 updated; + __u32 used; + __u8 remote_addr[sizeof(union vxlan_addr)]; + __u8 remote_port_lo; + __u8 remote_port_hi; + __u8 remote_vni; + __u32 remote_ifindex; + __u8 pad[4]; +}; + + + /* Flags that are used in the receive path. These flags must match in * order for a socket to be shareable */