Merge branch 'ppp-rtnetlink'

Guillaume Nault says:

====================
ppp: add rtnetlink support

PPP devices lack the ability to be customised at creation time. In
particular they can't be created in a given netns or with a particular
name. Moving or renaming the device after creation is possible, but
creates undesirable transient effects on servers where PPP devices are
constantly created and removed, as users connect and disconnect.
Implementing rtnetlink support solves this problem.

The rtnetlink handlers implemented in this series are minimal, and can
only replace the PPPIOCNEWUNIT ioctl. The rest of PPP ioctls remains
necessary for any other operation on channels and units.
It is perfectly possible to mix PPP devices created by rtnl
and by ioctl(PPPIOCNEWUNIT). Devices will behave in the same way.

mutex_trylock() is used to resolve the locking issue wrt. locking
dependency between rtnl_lock() and ppp_mutex (see ppp_nl_newlink() in
patch #2).

A user visible difference brought by this series is that old PPP
interfaces (those created with ioctl(PPPIOCNEWUNIT)), can now be
removed by "ip link del", just like new rtnl based PPP devices.

Changes since v3:
  - Rebase on net-next.
  - Not an RFC anymore.

Changes since v2:
  - Define ->rtnl_link_ops for ioctl based PPP devices, so they can
    handle rtnl messages just like rtnl based ones (suggested by
    Stephen Hemminger).
  - Move back to original lock ordering between ppp_mutex and rtnl_lock
    to simplify patch series. Handle lock inversion issue using
    mutex_trylock() (suggested by Stephen Hemminger).
  - Do file descriptor lookup directly in ppp_nl_newlink(), to simplify
    ppp_dev_configure().

Changes since v1:
  - Rebase on net-next.
  - Invert locking order wrt. ppp_mutex and rtnl_lock and protect
    file->private_data with ppp_mutex.
====================

Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
David S. Miller 2016-04-29 16:09:45 -04:00
commit 92aff96ac3
2 changed files with 235 additions and 88 deletions

View File

@ -46,6 +46,7 @@
#include <linux/device.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/file.h>
#include <asm/unaligned.h>
#include <net/slhc_vj.h>
#include <linux/atomic.h>
@ -183,6 +184,12 @@ struct channel {
#endif /* CONFIG_PPP_MULTILINK */
};
struct ppp_config {
struct file *file;
s32 unit;
bool ifname_is_set;
};
/*
* SMP locking issues:
* Both the ppp.rlock and ppp.wlock locks protect the ppp.channels
@ -269,8 +276,7 @@ static void ppp_ccp_peek(struct ppp *ppp, struct sk_buff *skb, int inbound);
static void ppp_ccp_closed(struct ppp *ppp);
static struct compressor *find_compressor(int type);
static void ppp_get_stats(struct ppp *ppp, struct ppp_stats *st);
static struct ppp *ppp_create_interface(struct net *net, int unit,
struct file *file, int *retp);
static int ppp_create_interface(struct net *net, struct file *file, int *unit);
static void init_ppp_file(struct ppp_file *pf, int kind);
static void ppp_destroy_interface(struct ppp *ppp);
static struct ppp *ppp_find_unit(struct ppp_net *pn, int unit);
@ -282,6 +288,7 @@ static int unit_get(struct idr *p, void *ptr);
static int unit_set(struct idr *p, void *ptr, int n);
static void unit_put(struct idr *p, int n);
static void *unit_find(struct idr *p, int n);
static void ppp_setup(struct net_device *dev);
static const struct net_device_ops ppp_netdev_ops;
@ -853,12 +860,12 @@ static int ppp_unattached_ioctl(struct net *net, struct ppp_file *pf,
/* Create a new ppp unit */
if (get_user(unit, p))
break;
ppp = ppp_create_interface(net, unit, file, &err);
if (!ppp)
err = ppp_create_interface(net, file, &unit);
if (err < 0)
break;
file->private_data = &ppp->file;
err = -EFAULT;
if (put_user(ppp->file.index, p))
if (put_user(unit, p))
break;
err = 0;
break;
@ -960,6 +967,188 @@ static struct pernet_operations ppp_net_ops = {
.size = sizeof(struct ppp_net),
};
static int ppp_unit_register(struct ppp *ppp, int unit, bool ifname_is_set)
{
struct ppp_net *pn = ppp_pernet(ppp->ppp_net);
int ret;
mutex_lock(&pn->all_ppp_mutex);
if (unit < 0) {
ret = unit_get(&pn->units_idr, ppp);
if (ret < 0)
goto err;
} else {
/* Caller asked for a specific unit number. Fail with -EEXIST
* if unavailable. For backward compatibility, return -EEXIST
* too if idr allocation fails; this makes pppd retry without
* requesting a specific unit number.
*/
if (unit_find(&pn->units_idr, unit)) {
ret = -EEXIST;
goto err;
}
ret = unit_set(&pn->units_idr, ppp, unit);
if (ret < 0) {
/* Rewrite error for backward compatibility */
ret = -EEXIST;
goto err;
}
}
ppp->file.index = ret;
if (!ifname_is_set)
snprintf(ppp->dev->name, IFNAMSIZ, "ppp%i", ppp->file.index);
ret = register_netdevice(ppp->dev);
if (ret < 0)
goto err_unit;
atomic_inc(&ppp_unit_count);
mutex_unlock(&pn->all_ppp_mutex);
return 0;
err_unit:
unit_put(&pn->units_idr, ppp->file.index);
err:
mutex_unlock(&pn->all_ppp_mutex);
return ret;
}
static int ppp_dev_configure(struct net *src_net, struct net_device *dev,
const struct ppp_config *conf)
{
struct ppp *ppp = netdev_priv(dev);
int indx;
int err;
ppp->dev = dev;
ppp->ppp_net = src_net;
ppp->mru = PPP_MRU;
ppp->owner = conf->file;
init_ppp_file(&ppp->file, INTERFACE);
ppp->file.hdrlen = PPP_HDRLEN - 2; /* don't count proto bytes */
for (indx = 0; indx < NUM_NP; ++indx)
ppp->npmode[indx] = NPMODE_PASS;
INIT_LIST_HEAD(&ppp->channels);
spin_lock_init(&ppp->rlock);
spin_lock_init(&ppp->wlock);
#ifdef CONFIG_PPP_MULTILINK
ppp->minseq = -1;
skb_queue_head_init(&ppp->mrq);
#endif /* CONFIG_PPP_MULTILINK */
#ifdef CONFIG_PPP_FILTER
ppp->pass_filter = NULL;
ppp->active_filter = NULL;
#endif /* CONFIG_PPP_FILTER */
err = ppp_unit_register(ppp, conf->unit, conf->ifname_is_set);
if (err < 0)
return err;
conf->file->private_data = &ppp->file;
return 0;
}
static const struct nla_policy ppp_nl_policy[IFLA_PPP_MAX + 1] = {
[IFLA_PPP_DEV_FD] = { .type = NLA_S32 },
};
static int ppp_nl_validate(struct nlattr *tb[], struct nlattr *data[])
{
if (!data)
return -EINVAL;
if (!data[IFLA_PPP_DEV_FD])
return -EINVAL;
if (nla_get_s32(data[IFLA_PPP_DEV_FD]) < 0)
return -EBADF;
return 0;
}
static int ppp_nl_newlink(struct net *src_net, struct net_device *dev,
struct nlattr *tb[], struct nlattr *data[])
{
struct ppp_config conf = {
.unit = -1,
.ifname_is_set = true,
};
struct file *file;
int err;
file = fget(nla_get_s32(data[IFLA_PPP_DEV_FD]));
if (!file)
return -EBADF;
/* rtnl_lock is already held here, but ppp_create_interface() locks
* ppp_mutex before holding rtnl_lock. Using mutex_trylock() avoids
* possible deadlock due to lock order inversion, at the cost of
* pushing the problem back to userspace.
*/
if (!mutex_trylock(&ppp_mutex)) {
err = -EBUSY;
goto out;
}
if (file->f_op != &ppp_device_fops || file->private_data) {
err = -EBADF;
goto out_unlock;
}
conf.file = file;
err = ppp_dev_configure(src_net, dev, &conf);
out_unlock:
mutex_unlock(&ppp_mutex);
out:
fput(file);
return err;
}
static void ppp_nl_dellink(struct net_device *dev, struct list_head *head)
{
unregister_netdevice_queue(dev, head);
}
static size_t ppp_nl_get_size(const struct net_device *dev)
{
return 0;
}
static int ppp_nl_fill_info(struct sk_buff *skb, const struct net_device *dev)
{
return 0;
}
static struct net *ppp_nl_get_link_net(const struct net_device *dev)
{
struct ppp *ppp = netdev_priv(dev);
return ppp->ppp_net;
}
static struct rtnl_link_ops ppp_link_ops __read_mostly = {
.kind = "ppp",
.maxtype = IFLA_PPP_MAX,
.policy = ppp_nl_policy,
.priv_size = sizeof(struct ppp),
.setup = ppp_setup,
.validate = ppp_nl_validate,
.newlink = ppp_nl_newlink,
.dellink = ppp_nl_dellink,
.get_size = ppp_nl_get_size,
.fill_info = ppp_nl_fill_info,
.get_link_net = ppp_nl_get_link_net,
};
#define PPP_MAJOR 108
/* Called at boot time if ppp is compiled into the kernel,
@ -988,11 +1177,19 @@ static int __init ppp_init(void)
goto out_chrdev;
}
err = rtnl_link_register(&ppp_link_ops);
if (err) {
pr_err("failed to register rtnetlink PPP handler\n");
goto out_class;
}
/* not a big deal if we fail here :-) */
device_create(ppp_class, NULL, MKDEV(PPP_MAJOR, 0), NULL, "ppp");
return 0;
out_class:
class_destroy(ppp_class);
out_chrdev:
unregister_chrdev(PPP_MAJOR, "ppp");
out_net:
@ -2732,102 +2929,42 @@ ppp_get_stats(struct ppp *ppp, struct ppp_stats *st)
* or if there is already a unit with the requested number.
* unit == -1 means allocate a new number.
*/
static struct ppp *ppp_create_interface(struct net *net, int unit,
struct file *file, int *retp)
static int ppp_create_interface(struct net *net, struct file *file, int *unit)
{
struct ppp_config conf = {
.file = file,
.unit = *unit,
.ifname_is_set = false,
};
struct net_device *dev;
struct ppp *ppp;
struct ppp_net *pn;
struct net_device *dev = NULL;
int ret = -ENOMEM;
int i;
int err;
dev = alloc_netdev(sizeof(struct ppp), "", NET_NAME_ENUM, ppp_setup);
if (!dev)
goto out1;
pn = ppp_pernet(net);
ppp = netdev_priv(dev);
ppp->dev = dev;
ppp->mru = PPP_MRU;
init_ppp_file(&ppp->file, INTERFACE);
ppp->file.hdrlen = PPP_HDRLEN - 2; /* don't count proto bytes */
ppp->owner = file;
for (i = 0; i < NUM_NP; ++i)
ppp->npmode[i] = NPMODE_PASS;
INIT_LIST_HEAD(&ppp->channels);
spin_lock_init(&ppp->rlock);
spin_lock_init(&ppp->wlock);
#ifdef CONFIG_PPP_MULTILINK
ppp->minseq = -1;
skb_queue_head_init(&ppp->mrq);
#endif /* CONFIG_PPP_MULTILINK */
#ifdef CONFIG_PPP_FILTER
ppp->pass_filter = NULL;
ppp->active_filter = NULL;
#endif /* CONFIG_PPP_FILTER */
/*
* drum roll: don't forget to set
* the net device is belong to
*/
if (!dev) {
err = -ENOMEM;
goto err;
}
dev_net_set(dev, net);
dev->rtnl_link_ops = &ppp_link_ops;
rtnl_lock();
mutex_lock(&pn->all_ppp_mutex);
if (unit < 0) {
unit = unit_get(&pn->units_idr, ppp);
if (unit < 0) {
ret = unit;
goto out2;
}
} else {
ret = -EEXIST;
if (unit_find(&pn->units_idr, unit))
goto out2; /* unit already exists */
/*
* if caller need a specified unit number
* lets try to satisfy him, otherwise --
* he should better ask us for new unit number
*
* NOTE: yes I know that returning EEXIST it's not
* fair but at least pppd will ask us to allocate
* new unit in this case so user is happy :)
*/
unit = unit_set(&pn->units_idr, ppp, unit);
if (unit < 0)
goto out2;
}
err = ppp_dev_configure(net, dev, &conf);
if (err < 0)
goto err_dev;
ppp = netdev_priv(dev);
*unit = ppp->file.index;
/* Initialize the new ppp unit */
ppp->file.index = unit;
sprintf(dev->name, "ppp%d", unit);
ret = register_netdevice(dev);
if (ret != 0) {
unit_put(&pn->units_idr, unit);
netdev_err(ppp->dev, "PPP: couldn't register device %s (%d)\n",
dev->name, ret);
goto out2;
}
ppp->ppp_net = net;
atomic_inc(&ppp_unit_count);
mutex_unlock(&pn->all_ppp_mutex);
rtnl_unlock();
*retp = 0;
return ppp;
return 0;
out2:
mutex_unlock(&pn->all_ppp_mutex);
err_dev:
rtnl_unlock();
free_netdev(dev);
out1:
*retp = ret;
return NULL;
err:
return err;
}
/*
@ -3016,6 +3153,7 @@ static void __exit ppp_cleanup(void)
/* should never happen */
if (atomic_read(&ppp_unit_count) || atomic_read(&channel_count))
pr_err("PPP: removing module but units remain!\n");
rtnl_link_unregister(&ppp_link_ops);
unregister_chrdev(PPP_MAJOR, "ppp");
device_destroy(ppp_class, MKDEV(PPP_MAJOR, 0));
class_destroy(ppp_class);
@ -3074,4 +3212,5 @@ EXPORT_SYMBOL(ppp_register_compressor);
EXPORT_SYMBOL(ppp_unregister_compressor);
MODULE_LICENSE("GPL");
MODULE_ALIAS_CHARDEV(PPP_MAJOR, 0);
MODULE_ALIAS_RTNL_LINK("ppp");
MODULE_ALIAS("devname:ppp");

View File

@ -520,6 +520,14 @@ enum {
};
#define IFLA_GENEVE_MAX (__IFLA_GENEVE_MAX - 1)
/* PPP section */
enum {
IFLA_PPP_UNSPEC,
IFLA_PPP_DEV_FD,
__IFLA_PPP_MAX
};
#define IFLA_PPP_MAX (__IFLA_PPP_MAX - 1)
/* Bonding section */
enum {