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: David S. Miller <davem@davemloft.net>
This commit is contained in:
David S. Miller 2020-05-19 15:45:12 -07:00
commit a890761798
9 changed files with 346 additions and 354 deletions

View File

@ -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);
};
/**

View File

@ -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);

View File

@ -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 | \

View File

@ -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)

View File

@ -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)

View File

@ -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 | \

View File

@ -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;
struct net_device *tunnel_dev, *new_dev;
struct ip_tunnel_parm p = { };
int err;
dev = __dev_get_by_name(net, "tunl0");
tunnel_dev = __dev_get_by_name(net, "tunl0");
if (!tunnel_dev)
goto out;
if (dev) {
const struct net_device_ops *ops = dev->netdev_ops;
int err;
struct ifreq ifr;
struct ip_tunnel_parm 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);
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 (!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;
if (ops->ndo_do_ioctl) {
mm_segment_t oldfs = get_fs();
new_dev = __dev_get_by_name(net, p.name);
if (!new_dev)
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);
}
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 dev;
return new_dev;
failure:
unregister_netdevice(dev);
return NULL;
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:

View File

@ -2783,6 +2783,33 @@ put:
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 @@ put:
*/
int addrconf_set_dstaddr(struct net *net, void __user *arg)
{
struct in6_ifreq ireq;
struct net_device *dev;
int err = -EINVAL;
struct in6_ifreq ireq;
int err = -ENODEV;
if (!IS_ENABLED(CONFIG_IPV6_SIT))
return -ENODEV;
if (copy_from_user(&ireq, arg, sizeof(struct in6_ifreq)))
return -EFAULT;
rtnl_lock();
err = -EFAULT;
if (copy_from_user(&ireq, arg, sizeof(struct in6_ifreq)))
goto err_exit;
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;
}

View File

@ -83,6 +83,13 @@ struct sit_net {
struct net_device *fb_tunnel_dev;
};
static inline struct sit_net *dev_to_sit_net(struct net_device *dev)
{
struct ip_tunnel *t = netdev_priv(dev);
return net_generic(t->net, sit_net_id);
}
/*
* Must be invoked with rcu_read_lock
*/
@ -291,14 +298,18 @@ __ipip6_tunnel_locate_prl(struct ip_tunnel *t, __be32 addr)
}
static int ipip6_tunnel_get_prl(struct ip_tunnel *t,
struct ip_tunnel_prl __user *a)
static int ipip6_tunnel_get_prl(struct net_device *dev, struct ifreq *ifr)
{
struct ip_tunnel_prl __user *a = ifr->ifr_ifru.ifru_data;
struct ip_tunnel *t = netdev_priv(dev);
struct ip_tunnel_prl kprl, *kp;
struct ip_tunnel_prl_entry *prl;
unsigned int cmax, c = 0, ca, len;
int ret = 0;
if (dev == dev_to_sit_net(dev)->fb_tunnel_dev)
return -EINVAL;
if (copy_from_user(&kprl, a, sizeof(kprl)))
return -EFAULT;
cmax = kprl.datalen / sizeof(kprl);
@ -441,6 +452,35 @@ out:
return err;
}
static int ipip6_tunnel_prl_ctl(struct net_device *dev, struct ifreq *ifr,
int cmd)
{
struct ip_tunnel *t = netdev_priv(dev);
struct ip_tunnel_prl prl;
int err;
if (!ns_capable(t->net->user_ns, CAP_NET_ADMIN))
return -EPERM;
if (dev == dev_to_sit_net(dev)->fb_tunnel_dev)
return -EINVAL;
if (copy_from_user(&prl, ifr->ifr_ifru.ifru_data, sizeof(prl)))
return -EFAULT;
switch (cmd) {
case SIOCDELPRL:
err = ipip6_tunnel_del_prl(t, &prl);
break;
case SIOCADDPRL:
case SIOCCHGPRL:
err = ipip6_tunnel_add_prl(t, &prl, cmd == SIOCCHGPRL);
break;
}
dst_cache_reset(&t->dst_cache);
netdev_state_change(dev);
return err;
}
static int
isatap_chksrc(struct sk_buff *skb, const struct iphdr *iph, struct ip_tunnel *t)
{
@ -1151,7 +1191,53 @@ static int ipip6_tunnel_update_6rd(struct ip_tunnel *t,
netdev_state_change(t->dev);
return 0;
}
#endif
static int
ipip6_tunnel_get6rd(struct net_device *dev, struct ifreq *ifr)
{
struct ip_tunnel *t = netdev_priv(dev);
struct ip_tunnel_6rd ip6rd;
struct ip_tunnel_parm p;
if (dev == dev_to_sit_net(dev)->fb_tunnel_dev) {
if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, sizeof(p)))
return -EFAULT;
t = ipip6_tunnel_locate(t->net, &p, 0);
}
if (!t)
t = netdev_priv(dev);
ip6rd.prefix = t->ip6rd.prefix;
ip6rd.relay_prefix = t->ip6rd.relay_prefix;
ip6rd.prefixlen = t->ip6rd.prefixlen;
ip6rd.relay_prefixlen = t->ip6rd.relay_prefixlen;
if (copy_to_user(ifr->ifr_ifru.ifru_data, &ip6rd, sizeof(ip6rd)))
return -EFAULT;
return 0;
}
static int
ipip6_tunnel_6rdctl(struct net_device *dev, struct ifreq *ifr, int cmd)
{
struct ip_tunnel *t = netdev_priv(dev);
struct ip_tunnel_6rd ip6rd;
int err;
if (!ns_capable(t->net->user_ns, CAP_NET_ADMIN))
return -EPERM;
if (copy_from_user(&ip6rd, ifr->ifr_ifru.ifru_data, sizeof(ip6rd)))
return -EFAULT;
if (cmd != SIOCDEL6RD) {
err = ipip6_tunnel_update_6rd(t, &ip6rd);
if (err < 0)
return err;
} else
ipip6_tunnel_clone_6rd(dev, dev_to_sit_net(dev));
return 0;
}
#endif /* CONFIG_IPV6_SIT_6RD */
static bool ipip6_valid_ip_proto(u8 ipproto)
{
@ -1164,185 +1250,145 @@ static bool ipip6_valid_ip_proto(u8 ipproto)
}
static int
ipip6_tunnel_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
__ipip6_tunnel_ioctl_validate(struct net *net, struct ip_tunnel_parm *p)
{
int err = 0;
struct ip_tunnel_parm p;
struct ip_tunnel_prl prl;
struct ip_tunnel *t = netdev_priv(dev);
struct net *net = t->net;
struct sit_net *sitn = net_generic(net, sit_net_id);
#ifdef CONFIG_IPV6_SIT_6RD
struct ip_tunnel_6rd ip6rd;
#endif
if (!ns_capable(net->user_ns, CAP_NET_ADMIN))
return -EPERM;
if (!ipip6_valid_ip_proto(p->iph.protocol))
return -EINVAL;
if (p->iph.version != 4 ||
p->iph.ihl != 5 || (p->iph.frag_off & htons(~IP_DF)))
return -EINVAL;
if (p->iph.ttl)
p->iph.frag_off |= htons(IP_DF);
return 0;
}
static int
ipip6_tunnel_get(struct net_device *dev, struct ip_tunnel_parm *p)
{
struct ip_tunnel *t = netdev_priv(dev);
if (dev == dev_to_sit_net(dev)->fb_tunnel_dev)
t = ipip6_tunnel_locate(t->net, p, 0);
if (!t)
t = netdev_priv(dev);
memcpy(p, &t->parms, sizeof(*p));
return 0;
}
static int
ipip6_tunnel_add(struct net_device *dev, struct ip_tunnel_parm *p)
{
struct ip_tunnel *t = netdev_priv(dev);
int err;
err = __ipip6_tunnel_ioctl_validate(t->net, p);
if (err)
return err;
t = ipip6_tunnel_locate(t->net, p, 1);
if (!t)
return -ENOBUFS;
return 0;
}
static int
ipip6_tunnel_change(struct net_device *dev, struct ip_tunnel_parm *p)
{
struct ip_tunnel *t = netdev_priv(dev);
int err;
err = __ipip6_tunnel_ioctl_validate(t->net, p);
if (err)
return err;
t = ipip6_tunnel_locate(t->net, p, 0);
if (dev == dev_to_sit_net(dev)->fb_tunnel_dev) {
if (!t)
return -ENOENT;
} else {
if (t) {
if (t->dev != dev)
return -EEXIST;
} else {
if (((dev->flags & IFF_POINTOPOINT) && !p->iph.daddr) ||
(!(dev->flags & IFF_POINTOPOINT) && p->iph.daddr))
return -EINVAL;
t = netdev_priv(dev);
}
ipip6_tunnel_update(t, p, t->fwmark);
}
return 0;
}
static int
ipip6_tunnel_del(struct net_device *dev, struct ip_tunnel_parm *p)
{
struct ip_tunnel *t = netdev_priv(dev);
if (!ns_capable(t->net->user_ns, CAP_NET_ADMIN))
return -EPERM;
if (dev == dev_to_sit_net(dev)->fb_tunnel_dev) {
t = ipip6_tunnel_locate(t->net, p, 0);
if (!t)
return -ENOENT;
if (t == netdev_priv(dev_to_sit_net(dev)->fb_tunnel_dev))
return -EPERM;
dev = t->dev;
}
unregister_netdevice(dev);
return 0;
}
static int
ipip6_tunnel_ctl(struct net_device *dev, struct ip_tunnel_parm *p, int cmd)
{
switch (cmd) {
case SIOCGETTUNNEL:
#ifdef CONFIG_IPV6_SIT_6RD
case SIOCGET6RD:
#endif
if (dev == sitn->fb_tunnel_dev) {
if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, sizeof(p))) {
err = -EFAULT;
break;
}
t = ipip6_tunnel_locate(net, &p, 0);
if (!t)
t = netdev_priv(dev);
}
err = -EFAULT;
if (cmd == SIOCGETTUNNEL) {
memcpy(&p, &t->parms, sizeof(p));
if (copy_to_user(ifr->ifr_ifru.ifru_data, &p,
sizeof(p)))
goto done;
#ifdef CONFIG_IPV6_SIT_6RD
} else {
ip6rd.prefix = t->ip6rd.prefix;
ip6rd.relay_prefix = t->ip6rd.relay_prefix;
ip6rd.prefixlen = t->ip6rd.prefixlen;
ip6rd.relay_prefixlen = t->ip6rd.relay_prefixlen;
if (copy_to_user(ifr->ifr_ifru.ifru_data, &ip6rd,
sizeof(ip6rd)))
goto done;
#endif
}
err = 0;
break;
return ipip6_tunnel_get(dev, p);
case SIOCADDTUNNEL:
return ipip6_tunnel_add(dev, p);
case SIOCCHGTUNNEL:
return ipip6_tunnel_change(dev, p);
case SIOCDELTUNNEL:
return ipip6_tunnel_del(dev, p);
default:
return -EINVAL;
}
}
static int
ipip6_tunnel_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
{
switch (cmd) {
case SIOCGETTUNNEL:
case SIOCADDTUNNEL:
case SIOCCHGTUNNEL:
err = -EPERM;
if (!ns_capable(net->user_ns, CAP_NET_ADMIN))
goto done;
err = -EFAULT;
if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, sizeof(p)))
goto done;
err = -EINVAL;
if (!ipip6_valid_ip_proto(p.iph.protocol))
goto done;
if (p.iph.version != 4 ||
p.iph.ihl != 5 || (p.iph.frag_off&htons(~IP_DF)))
goto done;
if (p.iph.ttl)
p.iph.frag_off |= htons(IP_DF);
t = ipip6_tunnel_locate(net, &p, cmd == SIOCADDTUNNEL);
if (dev != sitn->fb_tunnel_dev && cmd == SIOCCHGTUNNEL) {
if (t) {
if (t->dev != dev) {
err = -EEXIST;
break;
}
} else {
if (((dev->flags&IFF_POINTOPOINT) && !p.iph.daddr) ||
(!(dev->flags&IFF_POINTOPOINT) && p.iph.daddr)) {
err = -EINVAL;
break;
}
t = netdev_priv(dev);
}
ipip6_tunnel_update(t, &p, t->fwmark);
}
if (t) {
err = 0;
if (copy_to_user(ifr->ifr_ifru.ifru_data, &t->parms, sizeof(p)))
err = -EFAULT;
} else
err = (cmd == SIOCADDTUNNEL ? -ENOBUFS : -ENOENT);
break;
case SIOCDELTUNNEL:
err = -EPERM;
if (!ns_capable(net->user_ns, CAP_NET_ADMIN))
goto done;
if (dev == sitn->fb_tunnel_dev) {
err = -EFAULT;
if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, sizeof(p)))
goto done;
err = -ENOENT;
t = ipip6_tunnel_locate(net, &p, 0);
if (!t)
goto done;
err = -EPERM;
if (t == netdev_priv(sitn->fb_tunnel_dev))
goto done;
dev = t->dev;
}
unregister_netdevice(dev);
err = 0;
break;
return ip_tunnel_ioctl(dev, ifr, cmd);
case SIOCGETPRL:
err = -EINVAL;
if (dev == sitn->fb_tunnel_dev)
goto done;
err = ipip6_tunnel_get_prl(t, ifr->ifr_ifru.ifru_data);
break;
return ipip6_tunnel_get_prl(dev, ifr);
case SIOCADDPRL:
case SIOCDELPRL:
case SIOCCHGPRL:
err = -EPERM;
if (!ns_capable(net->user_ns, CAP_NET_ADMIN))
goto done;
err = -EINVAL;
if (dev == sitn->fb_tunnel_dev)
goto done;
err = -EFAULT;
if (copy_from_user(&prl, ifr->ifr_ifru.ifru_data, sizeof(prl)))
goto done;
switch (cmd) {
case SIOCDELPRL:
err = ipip6_tunnel_del_prl(t, &prl);
break;
case SIOCADDPRL:
case SIOCCHGPRL:
err = ipip6_tunnel_add_prl(t, &prl, cmd == SIOCCHGPRL);
break;
}
dst_cache_reset(&t->dst_cache);
netdev_state_change(dev);
break;
return ipip6_tunnel_prl_ctl(dev, ifr, cmd);
#ifdef CONFIG_IPV6_SIT_6RD
case SIOCGET6RD:
return ipip6_tunnel_get6rd(dev, ifr);
case SIOCADD6RD:
case SIOCCHG6RD:
case SIOCDEL6RD:
err = -EPERM;
if (!ns_capable(net->user_ns, CAP_NET_ADMIN))
goto done;
err = -EFAULT;
if (copy_from_user(&ip6rd, ifr->ifr_ifru.ifru_data,
sizeof(ip6rd)))
goto done;
if (cmd != SIOCDEL6RD) {
err = ipip6_tunnel_update_6rd(t, &ip6rd);
if (err < 0)
goto done;
} else
ipip6_tunnel_clone_6rd(dev, sitn);
err = 0;
break;
return ipip6_tunnel_6rdctl(dev, ifr, cmd);
#endif
default:
err = -EINVAL;
return -EINVAL;
}
done:
return err;
}
static const struct net_device_ops ipip6_netdev_ops = {
@ -1352,6 +1398,7 @@ static const struct net_device_ops ipip6_netdev_ops = {
.ndo_do_ioctl = ipip6_tunnel_ioctl,
.ndo_get_stats64 = ip_tunnel_get_stats64,
.ndo_get_iflink = ip_tunnel_get_iflink,
.ndo_tunnel_ctl = ipip6_tunnel_ctl,
};
static void ipip6_dev_free(struct net_device *dev)