Commit a8907617 authored by David S. Miller's avatar David S. Miller

Merge branch 'ndo_tunnel_ioctl'

Christoph Hellwig says:

====================
add a new ->ndo_tunnel_ctl method to avoid a few set_fs calls v2

both the ipv4 and ipv6 code have an ioctl each that can be used to create
a tunnel using code that doesn't live in the core kernel or ipv6 module.
Currently they call ioctls on the tunnel devices to create these, for
which the code needs to override the address limit, which is a "feature"
I plan to get rid of.

Instead this patchset adds a new ->ndo_tunnel_ctl that can be used for
the tunnel configuration using struct ip_tunnel_parm.  The method is
either invoked from a helper that does the uaccess and can be wired up
as ndo_do_ioctl method, or directly from the magic IPV4/6 ioctls that
create tunnels with kernel space arguments.

Changes since v2:
 - properly propagate errors in ipip6_tunnel_prl_ctl
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents befb270f 8e3db0bb
......@@ -53,6 +53,7 @@ struct netpoll_info;
struct device;
struct phy_device;
struct dsa_port;
struct ip_tunnel_parm;
struct macsec_context;
struct macsec_ops;
......@@ -1274,6 +1275,9 @@ struct netdev_net_notifier {
* Get devlink port instance associated with a given netdev.
* Called with a reference on the netdevice and devlink locks only,
* rtnl_lock is not held.
* int (*ndo_tunnel_ctl)(struct net_device *dev, struct ip_tunnel_parm *p,
* int cmd);
* Add, change, delete or get information on an IPv4 tunnel.
*/
struct net_device_ops {
int (*ndo_init)(struct net_device *dev);
......@@ -1479,6 +1483,8 @@ struct net_device_ops {
int (*ndo_xsk_wakeup)(struct net_device *dev,
u32 queue_id, u32 flags);
struct devlink_port * (*ndo_get_devlink_port)(struct net_device *dev);
int (*ndo_tunnel_ctl)(struct net_device *dev,
struct ip_tunnel_parm *p, int cmd);
};
/**
......
......@@ -269,7 +269,8 @@ void ip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
const struct iphdr *tnl_params, const u8 protocol);
void ip_md_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
const u8 proto, int tunnel_hlen);
int ip_tunnel_ioctl(struct net_device *dev, struct ip_tunnel_parm *p, int cmd);
int ip_tunnel_ctl(struct net_device *dev, struct ip_tunnel_parm *p, int cmd);
int ip_tunnel_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd);
int __ip_tunnel_change_mtu(struct net_device *dev, int new_mtu, bool strict);
int ip_tunnel_change_mtu(struct net_device *dev, int new_mtu);
......
......@@ -768,45 +768,37 @@ static void ipgre_link_update(struct net_device *dev, bool set_mtu)
}
}
static int ipgre_tunnel_ioctl(struct net_device *dev,
struct ifreq *ifr, int cmd)
static int ipgre_tunnel_ctl(struct net_device *dev, struct ip_tunnel_parm *p,
int cmd)
{
struct ip_tunnel_parm p;
int err;
if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, sizeof(p)))
return -EFAULT;
if (cmd == SIOCADDTUNNEL || cmd == SIOCCHGTUNNEL) {
if (p.iph.version != 4 || p.iph.protocol != IPPROTO_GRE ||
p.iph.ihl != 5 || (p.iph.frag_off & htons(~IP_DF)) ||
((p.i_flags | p.o_flags) & (GRE_VERSION | GRE_ROUTING)))
if (p->iph.version != 4 || p->iph.protocol != IPPROTO_GRE ||
p->iph.ihl != 5 || (p->iph.frag_off & htons(~IP_DF)) ||
((p->i_flags | p->o_flags) & (GRE_VERSION | GRE_ROUTING)))
return -EINVAL;
}
p.i_flags = gre_flags_to_tnl_flags(p.i_flags);
p.o_flags = gre_flags_to_tnl_flags(p.o_flags);
p->i_flags = gre_flags_to_tnl_flags(p->i_flags);
p->o_flags = gre_flags_to_tnl_flags(p->o_flags);
err = ip_tunnel_ioctl(dev, &p, cmd);
err = ip_tunnel_ctl(dev, p, cmd);
if (err)
return err;
if (cmd == SIOCCHGTUNNEL) {
struct ip_tunnel *t = netdev_priv(dev);
t->parms.i_flags = p.i_flags;
t->parms.o_flags = p.o_flags;
t->parms.i_flags = p->i_flags;
t->parms.o_flags = p->o_flags;
if (strcmp(dev->rtnl_link_ops->kind, "erspan"))
ipgre_link_update(dev, true);
}
p.i_flags = gre_tnl_flags_to_gre_flags(p.i_flags);
p.o_flags = gre_tnl_flags_to_gre_flags(p.o_flags);
if (copy_to_user(ifr->ifr_ifru.ifru_data, &p, sizeof(p)))
return -EFAULT;
p->i_flags = gre_tnl_flags_to_gre_flags(p->i_flags);
p->o_flags = gre_tnl_flags_to_gre_flags(p->o_flags);
return 0;
}
......@@ -924,10 +916,11 @@ static const struct net_device_ops ipgre_netdev_ops = {
.ndo_stop = ipgre_close,
#endif
.ndo_start_xmit = ipgre_xmit,
.ndo_do_ioctl = ipgre_tunnel_ioctl,
.ndo_do_ioctl = ip_tunnel_ioctl,
.ndo_change_mtu = ip_tunnel_change_mtu,
.ndo_get_stats64 = ip_tunnel_get_stats64,
.ndo_get_iflink = ip_tunnel_get_iflink,
.ndo_tunnel_ctl = ipgre_tunnel_ctl,
};
#define GRE_FEATURES (NETIF_F_SG | \
......
......@@ -860,7 +860,7 @@ static void ip_tunnel_update(struct ip_tunnel_net *itn,
netdev_state_change(dev);
}
int ip_tunnel_ioctl(struct net_device *dev, struct ip_tunnel_parm *p, int cmd)
int ip_tunnel_ctl(struct net_device *dev, struct ip_tunnel_parm *p, int cmd)
{
int err = 0;
struct ip_tunnel *t = netdev_priv(dev);
......@@ -960,6 +960,20 @@ int ip_tunnel_ioctl(struct net_device *dev, struct ip_tunnel_parm *p, int cmd)
done:
return err;
}
EXPORT_SYMBOL_GPL(ip_tunnel_ctl);
int ip_tunnel_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
{
struct ip_tunnel_parm p;
int err;
if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, sizeof(p)))
return -EFAULT;
err = dev->netdev_ops->ndo_tunnel_ctl(dev, &p, cmd);
if (!err && copy_to_user(ifr->ifr_ifru.ifru_data, &p, sizeof(p)))
return -EFAULT;
return err;
}
EXPORT_SYMBOL_GPL(ip_tunnel_ioctl);
int __ip_tunnel_change_mtu(struct net_device *dev, int new_mtu, bool strict)
......
......@@ -378,38 +378,31 @@ static int vti4_err(struct sk_buff *skb, u32 info)
}
static int
vti_tunnel_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
vti_tunnel_ctl(struct net_device *dev, struct ip_tunnel_parm *p, int cmd)
{
int err = 0;
struct ip_tunnel_parm p;
if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, sizeof(p)))
return -EFAULT;
if (cmd == SIOCADDTUNNEL || cmd == SIOCCHGTUNNEL) {
if (p.iph.version != 4 || p.iph.protocol != IPPROTO_IPIP ||
p.iph.ihl != 5)
if (p->iph.version != 4 || p->iph.protocol != IPPROTO_IPIP ||
p->iph.ihl != 5)
return -EINVAL;
}
if (!(p.i_flags & GRE_KEY))
p.i_key = 0;
if (!(p.o_flags & GRE_KEY))
p.o_key = 0;
if (!(p->i_flags & GRE_KEY))
p->i_key = 0;
if (!(p->o_flags & GRE_KEY))
p->o_key = 0;
p.i_flags = VTI_ISVTI;
p->i_flags = VTI_ISVTI;
err = ip_tunnel_ioctl(dev, &p, cmd);
err = ip_tunnel_ctl(dev, p, cmd);
if (err)
return err;
if (cmd != SIOCDELTUNNEL) {
p.i_flags |= GRE_KEY;
p.o_flags |= GRE_KEY;
p->i_flags |= GRE_KEY;
p->o_flags |= GRE_KEY;
}
if (copy_to_user(ifr->ifr_ifru.ifru_data, &p, sizeof(p)))
return -EFAULT;
return 0;
}
......@@ -417,10 +410,11 @@ static const struct net_device_ops vti_netdev_ops = {
.ndo_init = vti_tunnel_init,
.ndo_uninit = ip_tunnel_uninit,
.ndo_start_xmit = vti_tunnel_xmit,
.ndo_do_ioctl = vti_tunnel_ioctl,
.ndo_do_ioctl = ip_tunnel_ioctl,
.ndo_change_mtu = ip_tunnel_change_mtu,
.ndo_get_stats64 = ip_tunnel_get_stats64,
.ndo_get_iflink = ip_tunnel_get_iflink,
.ndo_tunnel_ctl = vti_tunnel_ctl,
};
static void vti_tunnel_setup(struct net_device *dev)
......
......@@ -327,41 +327,29 @@ static bool ipip_tunnel_ioctl_verify_protocol(u8 ipproto)
}
static int
ipip_tunnel_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
ipip_tunnel_ctl(struct net_device *dev, struct ip_tunnel_parm *p, int cmd)
{
int err = 0;
struct ip_tunnel_parm p;
if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, sizeof(p)))
return -EFAULT;
if (cmd == SIOCADDTUNNEL || cmd == SIOCCHGTUNNEL) {
if (p.iph.version != 4 ||
!ipip_tunnel_ioctl_verify_protocol(p.iph.protocol) ||
p.iph.ihl != 5 || (p.iph.frag_off&htons(~IP_DF)))
if (p->iph.version != 4 ||
!ipip_tunnel_ioctl_verify_protocol(p->iph.protocol) ||
p->iph.ihl != 5 || (p->iph.frag_off & htons(~IP_DF)))
return -EINVAL;
}
p.i_key = p.o_key = 0;
p.i_flags = p.o_flags = 0;
err = ip_tunnel_ioctl(dev, &p, cmd);
if (err)
return err;
if (copy_to_user(ifr->ifr_ifru.ifru_data, &p, sizeof(p)))
return -EFAULT;
return 0;
p->i_key = p->o_key = 0;
p->i_flags = p->o_flags = 0;
return ip_tunnel_ctl(dev, p, cmd);
}
static const struct net_device_ops ipip_netdev_ops = {
.ndo_init = ipip_tunnel_init,
.ndo_uninit = ip_tunnel_uninit,
.ndo_start_xmit = ipip_tunnel_xmit,
.ndo_do_ioctl = ipip_tunnel_ioctl,
.ndo_do_ioctl = ip_tunnel_ioctl,
.ndo_change_mtu = ip_tunnel_change_mtu,
.ndo_get_stats64 = ip_tunnel_get_stats64,
.ndo_get_iflink = ip_tunnel_get_iflink,
.ndo_tunnel_ctl = ipip_tunnel_ctl,
};
#define IPIP_FEATURES (NETIF_F_SG | \
......
......@@ -421,37 +421,6 @@ static void ipmr_free_table(struct mr_table *mrt)
/* Service routines creating virtual interfaces: DVMRP tunnels and PIMREG */
static void ipmr_del_tunnel(struct net_device *dev, struct vifctl *v)
{
struct net *net = dev_net(dev);
dev_close(dev);
dev = __dev_get_by_name(net, "tunl0");
if (dev) {
const struct net_device_ops *ops = dev->netdev_ops;
struct ifreq ifr;
struct ip_tunnel_parm p;
memset(&p, 0, sizeof(p));
p.iph.daddr = v->vifc_rmt_addr.s_addr;
p.iph.saddr = v->vifc_lcl_addr.s_addr;
p.iph.version = 4;
p.iph.ihl = 5;
p.iph.protocol = IPPROTO_IPIP;
sprintf(p.name, "dvmrp%d", v->vifc_vifi);
ifr.ifr_ifru.ifru_data = (__force void __user *)&p;
if (ops->ndo_do_ioctl) {
mm_segment_t oldfs = get_fs();
set_fs(KERNEL_DS);
ops->ndo_do_ioctl(dev, &ifr, SIOCDELTUNNEL);
set_fs(oldfs);
}
}
}
/* Initialize ipmr pimreg/tunnel in_device */
static bool ipmr_init_vif_indev(const struct net_device *dev)
{
......@@ -471,51 +440,52 @@ static bool ipmr_init_vif_indev(const struct net_device *dev)
static struct net_device *ipmr_new_tunnel(struct net *net, struct vifctl *v)
{
struct net_device *dev;
dev = __dev_get_by_name(net, "tunl0");
struct net_device *tunnel_dev, *new_dev;
struct ip_tunnel_parm p = { };
int err;
if (dev) {
const struct net_device_ops *ops = dev->netdev_ops;
int err;
struct ifreq ifr;
struct ip_tunnel_parm p;
tunnel_dev = __dev_get_by_name(net, "tunl0");
if (!tunnel_dev)
goto out;
memset(&p, 0, sizeof(p));
p.iph.daddr = v->vifc_rmt_addr.s_addr;
p.iph.saddr = v->vifc_lcl_addr.s_addr;
p.iph.version = 4;
p.iph.ihl = 5;
p.iph.protocol = IPPROTO_IPIP;
sprintf(p.name, "dvmrp%d", v->vifc_vifi);
ifr.ifr_ifru.ifru_data = (__force void __user *)&p;
p.iph.daddr = v->vifc_rmt_addr.s_addr;
p.iph.saddr = v->vifc_lcl_addr.s_addr;
p.iph.version = 4;
p.iph.ihl = 5;
p.iph.protocol = IPPROTO_IPIP;
sprintf(p.name, "dvmrp%d", v->vifc_vifi);
if (ops->ndo_do_ioctl) {
mm_segment_t oldfs = get_fs();
if (!tunnel_dev->netdev_ops->ndo_tunnel_ctl)
goto out;
err = tunnel_dev->netdev_ops->ndo_tunnel_ctl(tunnel_dev, &p,
SIOCADDTUNNEL);
if (err)
goto out;
set_fs(KERNEL_DS);
err = ops->ndo_do_ioctl(dev, &ifr, SIOCADDTUNNEL);
set_fs(oldfs);
} else {
err = -EOPNOTSUPP;
}
dev = NULL;
if (err == 0 &&
(dev = __dev_get_by_name(net, p.name)) != NULL) {
dev->flags |= IFF_MULTICAST;
if (!ipmr_init_vif_indev(dev))
goto failure;
if (dev_open(dev, NULL))
goto failure;
dev_hold(dev);
}
}
return dev;
new_dev = __dev_get_by_name(net, p.name);
if (!new_dev)
goto out;
failure:
unregister_netdevice(dev);
return NULL;
new_dev->flags |= IFF_MULTICAST;
if (!ipmr_init_vif_indev(new_dev))
goto out_unregister;
if (dev_open(new_dev, NULL))
goto out_unregister;
dev_hold(new_dev);
err = dev_set_allmulti(new_dev, 1);
if (err) {
dev_close(new_dev);
tunnel_dev->netdev_ops->ndo_tunnel_ctl(tunnel_dev, &p,
SIOCDELTUNNEL);
dev_put(new_dev);
new_dev = ERR_PTR(err);
}
return new_dev;
out_unregister:
unregister_netdevice(new_dev);
out:
return ERR_PTR(-ENOBUFS);
}
#if defined(CONFIG_IP_PIMSM_V1) || defined(CONFIG_IP_PIMSM_V2)
......@@ -867,14 +837,8 @@ static int vif_add(struct net *net, struct mr_table *mrt,
break;
case VIFF_TUNNEL:
dev = ipmr_new_tunnel(net, vifc);
if (!dev)
return -ENOBUFS;
err = dev_set_allmulti(dev, 1);
if (err) {
ipmr_del_tunnel(dev, vifc);
dev_put(dev);
return err;
}
if (IS_ERR(dev))
return PTR_ERR(dev);
break;
case VIFF_USE_IFINDEX:
case 0:
......
......@@ -2783,6 +2783,33 @@ void addrconf_prefix_rcv(struct net_device *dev, u8 *opt, int len, bool sllao)
in6_dev_put(in6_dev);
}
static int addrconf_set_sit_dstaddr(struct net *net, struct net_device *dev,
struct in6_ifreq *ireq)
{
struct ip_tunnel_parm p = { };
int err;
if (!(ipv6_addr_type(&ireq->ifr6_addr) & IPV6_ADDR_COMPATv4))
return -EADDRNOTAVAIL;
p.iph.daddr = ireq->ifr6_addr.s6_addr32[3];
p.iph.version = 4;
p.iph.ihl = 5;
p.iph.protocol = IPPROTO_IPV6;
p.iph.ttl = 64;
if (!dev->netdev_ops->ndo_tunnel_ctl)
return -EOPNOTSUPP;
err = dev->netdev_ops->ndo_tunnel_ctl(dev, &p, SIOCADDTUNNEL);
if (err)
return err;
dev = __dev_get_by_name(net, p.name);
if (!dev)
return -ENOBUFS;
return dev_open(dev, NULL);
}
/*
* Set destination address.
* Special case for SIT interfaces where we create a new "virtual"
......@@ -2790,61 +2817,19 @@ void addrconf_prefix_rcv(struct net_device *dev, u8 *opt, int len, bool sllao)
*/
int addrconf_set_dstaddr(struct net *net, void __user *arg)
{
struct in6_ifreq ireq;
struct net_device *dev;
int err = -EINVAL;
rtnl_lock();
struct in6_ifreq ireq;
int err = -ENODEV;
err = -EFAULT;
if (!IS_ENABLED(CONFIG_IPV6_SIT))
return -ENODEV;
if (copy_from_user(&ireq, arg, sizeof(struct in6_ifreq)))
goto err_exit;
return -EFAULT;
rtnl_lock();
dev = __dev_get_by_index(net, ireq.ifr6_ifindex);
err = -ENODEV;
if (!dev)
goto err_exit;
#if IS_ENABLED(CONFIG_IPV6_SIT)
if (dev->type == ARPHRD_SIT) {
const struct net_device_ops *ops = dev->netdev_ops;
struct ifreq ifr;
struct ip_tunnel_parm p;
err = -EADDRNOTAVAIL;
if (!(ipv6_addr_type(&ireq.ifr6_addr) & IPV6_ADDR_COMPATv4))
goto err_exit;
memset(&p, 0, sizeof(p));
p.iph.daddr = ireq.ifr6_addr.s6_addr32[3];
p.iph.saddr = 0;
p.iph.version = 4;
p.iph.ihl = 5;
p.iph.protocol = IPPROTO_IPV6;
p.iph.ttl = 64;
ifr.ifr_ifru.ifru_data = (__force void __user *)&p;
if (ops->ndo_do_ioctl) {
mm_segment_t oldfs = get_fs();
set_fs(KERNEL_DS);
err = ops->ndo_do_ioctl(dev, &ifr, SIOCADDTUNNEL);
set_fs(oldfs);
} else
err = -EOPNOTSUPP;
if (err == 0) {
err = -ENOBUFS;
dev = __dev_get_by_name(net, p.name);
if (!dev)
goto err_exit;
err = dev_open(dev, NULL);
}
}
#endif
err_exit:
if (dev && dev->type == ARPHRD_SIT)
err = addrconf_set_sit_dstaddr(net, dev, &ireq);
rtnl_unlock();
return err;
}
......
This diff is collapsed.
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