blob: 5de5f7867d8a544cb5ebfbf91aeb09088b3b58da [file] [log] [blame]
/*
* MPTCP implementation - MPTCP-subflow-management
*
* Initial Design & Implementation:
* Sébastien Barré <sebastien.barre@uclouvain.be>
*
* Current Maintainer & Author:
* Christoph Paasch <christoph.paasch@uclouvain.be>
*
* Additional authors:
* Jaakko Korkeaniemi <jaakko.korkeaniemi@aalto.fi>
* Gregory Detal <gregory.detal@uclouvain.be>
* Fabien Duchêne <fabien.duchene@uclouvain.be>
* Andreas Seelinger <Andreas.Seelinger@rwth-aachen.de>
* Lavkesh Lahngir <lavkesh51@gmail.com>
* Andreas Ripke <ripke@neclab.eu>
* Vlad Dogaru <vlad.dogaru@intel.com>
* Octavian Purdila <octavian.purdila@intel.com>
* John Ronan <jronan@tssg.org>
* Catalin Nicutar <catalin.nicutar@gmail.com>
* Brandon Heller <brandonh@stanford.edu>
*
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
#include <linux/module.h>
#include <net/mptcp.h>
static DEFINE_SPINLOCK(mptcp_pm_list_lock);
static LIST_HEAD(mptcp_pm_list);
static int mptcp_default_id(sa_family_t family, union inet_addr *addr,
struct net *net, bool *low_prio)
{
return 0;
}
struct mptcp_pm_ops mptcp_pm_default = {
.get_local_id = mptcp_default_id, /* We do not care */
.name = "default",
.owner = THIS_MODULE,
};
static struct mptcp_pm_ops *mptcp_pm_find(const char *name)
{
struct mptcp_pm_ops *e;
list_for_each_entry_rcu(e, &mptcp_pm_list, list) {
if (strcmp(e->name, name) == 0)
return e;
}
return NULL;
}
int mptcp_register_path_manager(struct mptcp_pm_ops *pm)
{
int ret = 0;
if (!pm->get_local_id)
return -EINVAL;
spin_lock(&mptcp_pm_list_lock);
if (mptcp_pm_find(pm->name)) {
pr_notice("%s already registered\n", pm->name);
ret = -EEXIST;
} else {
list_add_tail_rcu(&pm->list, &mptcp_pm_list);
pr_info("%s registered\n", pm->name);
}
spin_unlock(&mptcp_pm_list_lock);
return ret;
}
EXPORT_SYMBOL_GPL(mptcp_register_path_manager);
void mptcp_unregister_path_manager(struct mptcp_pm_ops *pm)
{
spin_lock(&mptcp_pm_list_lock);
list_del_rcu(&pm->list);
spin_unlock(&mptcp_pm_list_lock);
/* Wait for outstanding readers to complete before the
* module gets removed entirely.
*
* A try_module_get() should fail by now as our module is
* in "going" state since no refs are held anymore and
* module_exit() handler being called.
*/
synchronize_rcu();
}
EXPORT_SYMBOL_GPL(mptcp_unregister_path_manager);
void mptcp_get_default_path_manager(char *name)
{
struct mptcp_pm_ops *pm;
BUG_ON(list_empty(&mptcp_pm_list));
rcu_read_lock();
pm = list_entry(mptcp_pm_list.next, struct mptcp_pm_ops, list);
strncpy(name, pm->name, MPTCP_PM_NAME_MAX);
rcu_read_unlock();
}
int mptcp_set_default_path_manager(const char *name)
{
struct mptcp_pm_ops *pm;
int ret = -ENOENT;
spin_lock(&mptcp_pm_list_lock);
pm = mptcp_pm_find(name);
#ifdef CONFIG_MODULES
if (!pm && capable(CAP_NET_ADMIN)) {
spin_unlock(&mptcp_pm_list_lock);
request_module("mptcp_%s", name);
spin_lock(&mptcp_pm_list_lock);
pm = mptcp_pm_find(name);
}
#endif
if (pm) {
list_move(&pm->list, &mptcp_pm_list);
ret = 0;
} else {
pr_info("%s is not available\n", name);
}
spin_unlock(&mptcp_pm_list_lock);
return ret;
}
static struct mptcp_pm_ops *__mptcp_pm_find_autoload(const char *name)
{
struct mptcp_pm_ops *pm = mptcp_pm_find(name);
#ifdef CONFIG_MODULES
if (!pm && capable(CAP_NET_ADMIN)) {
rcu_read_unlock();
request_module("mptcp_%s", name);
rcu_read_lock();
pm = mptcp_pm_find(name);
}
#endif
return pm;
}
void mptcp_init_path_manager(struct mptcp_cb *mpcb)
{
struct mptcp_pm_ops *pm;
struct sock *meta_sk = mpcb->meta_sk;
struct tcp_sock *meta_tp = tcp_sk(meta_sk);
rcu_read_lock();
/* if path manager was set using socket option */
if (meta_tp->mptcp_pm_setsockopt) {
pm = __mptcp_pm_find_autoload(meta_tp->mptcp_pm_name);
if (pm && try_module_get(pm->owner)) {
mpcb->pm_ops = pm;
goto out;
}
}
list_for_each_entry_rcu(pm, &mptcp_pm_list, list) {
if (try_module_get(pm->owner)) {
mpcb->pm_ops = pm;
break;
}
}
out:
rcu_read_unlock();
}
/* Change path manager for socket */
int mptcp_set_path_manager(struct sock *sk, const char *name)
{
struct mptcp_pm_ops *pm;
int err = 0;
rcu_read_lock();
pm = __mptcp_pm_find_autoload(name);
if (!pm) {
err = -ENOENT;
} else if (!ns_capable(sock_net(sk)->user_ns, CAP_NET_ADMIN)) {
err = -EPERM;
} else {
strcpy(tcp_sk(sk)->mptcp_pm_name, name);
tcp_sk(sk)->mptcp_pm_setsockopt = 1;
}
rcu_read_unlock();
return err;
}
/* Manage refcounts on socket close. */
void mptcp_cleanup_path_manager(struct mptcp_cb *mpcb)
{
module_put(mpcb->pm_ops->owner);
}
/* Fallback to the default path-manager. */
void mptcp_fallback_default(struct mptcp_cb *mpcb)
{
struct mptcp_pm_ops *pm;
mptcp_cleanup_path_manager(mpcb);
pm = mptcp_pm_find("default");
/* Cannot fail - it's the default module */
try_module_get(pm->owner);
mpcb->pm_ops = pm;
}
EXPORT_SYMBOL_GPL(mptcp_fallback_default);
/* Set default value from kernel configuration at bootup */
static int __init mptcp_path_manager_default(void)
{
return mptcp_set_default_path_manager("fullmesh");
}
late_initcall(mptcp_path_manager_default);