blob: 001a3f8a974d82c54a0782e8c118a7222cd48964 [file] [log] [blame]
/*
* MPTCP Scheduler to reduce latency and jitter.
*
* This scheduler sends all packets redundantly on all available subflows.
*
* Initial Design & Implementation:
* Tobias Erbshaeusser <erbshauesser@dvs.tu-darmstadt.de>
* Alexander Froemmgen <froemmge@dvs.tu-darmstadt.de>
*
* Initial corrections & modifications:
* Christian Pinedo <christian.pinedo@ehu.eus>
* Igor Lopez <igor.lopez@ehu.eus>
*
* 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>
/* Struct to store the data of a single subflow */
struct redsched_sock_data {
/* The skb or NULL */
struct sk_buff *skb;
/* End sequence number of the skb. This number should be checked
* to be valid before the skb field is used
*/
u32 skb_end_seq;
};
/* Struct to store the data of the control block */
struct redsched_cb_data {
/* The next subflow where a skb should be sent or NULL */
struct tcp_sock *next_subflow;
};
/* Returns the socket data from a given subflow socket */
static struct redsched_sock_data *redsched_get_sock_data(struct tcp_sock *tp)
{
return (struct redsched_sock_data *)&tp->mptcp->mptcp_sched[0];
}
/* Returns the control block data from a given meta socket */
static struct redsched_cb_data *redsched_get_cb_data(struct tcp_sock *tp)
{
return (struct redsched_cb_data *)&tp->mpcb->mptcp_sched[0];
}
static bool redsched_get_active_valid_sks(struct sock *meta_sk)
{
struct tcp_sock *meta_tp = tcp_sk(meta_sk);
struct mptcp_cb *mpcb = meta_tp->mpcb;
struct sock *sk;
int active_valid_sks = 0;
mptcp_for_each_sk(mpcb, sk) {
if (subflow_is_active((struct tcp_sock *)sk) &&
!mptcp_is_def_unavailable(sk))
active_valid_sks++;
}
return active_valid_sks;
}
static bool redsched_use_subflow(struct sock *meta_sk,
int active_valid_sks,
struct tcp_sock *tp,
struct sk_buff *skb)
{
if (!skb || !mptcp_is_available((struct sock *)tp, skb, false))
return false;
if (TCP_SKB_CB(skb)->path_mask != 0)
return subflow_is_active(tp);
if (TCP_SKB_CB(skb)->path_mask == 0) {
if (active_valid_sks == -1)
active_valid_sks = redsched_get_active_valid_sks(meta_sk);
if (subflow_is_backup(tp) && active_valid_sks > 0)
return false;
else
return true;
}
return false;
}
static struct sock *redundant_get_subflow(struct sock *meta_sk,
struct sk_buff *skb,
bool zero_wnd_test)
{
struct tcp_sock *meta_tp = tcp_sk(meta_sk);
struct mptcp_cb *mpcb = meta_tp->mpcb;
struct redsched_cb_data *cb_data = redsched_get_cb_data(meta_tp);
struct tcp_sock *first_tp = cb_data->next_subflow;
struct sock *sk;
struct tcp_sock *tp;
/* Answer data_fin on same subflow */
if (meta_sk->sk_shutdown & RCV_SHUTDOWN &&
skb && mptcp_is_data_fin(skb)) {
mptcp_for_each_sk(mpcb, sk) {
if (tcp_sk(sk)->mptcp->path_index ==
mpcb->dfin_path_index &&
mptcp_is_available(sk, skb, zero_wnd_test))
return sk;
}
}
if (!first_tp)
first_tp = mpcb->connection_list;
tp = first_tp;
/* still NULL (no subflow in connection_list?) */
if (!first_tp)
return NULL;
/* Search for any subflow to send it */
do {
if (mptcp_is_available((struct sock *)tp, skb,
zero_wnd_test)) {
cb_data->next_subflow = tp->mptcp->next;
return (struct sock *)tp;
}
tp = tp->mptcp->next;
if (!tp)
tp = mpcb->connection_list;
} while (tp != first_tp);
/* No space */
return NULL;
}
/* Corrects the stored skb pointers if they are invalid */
static void redsched_correct_skb_pointers(struct sock *meta_sk,
struct redsched_sock_data *sk_data)
{
struct tcp_sock *meta_tp = tcp_sk(meta_sk);
if (sk_data->skb && !after(sk_data->skb_end_seq, meta_tp->snd_una))
sk_data->skb = NULL;
}
/* Returns the next skb from the queue */
static struct sk_buff *redundant_next_skb_from_queue(struct sk_buff_head *queue,
struct sk_buff *previous,
struct sock *meta_sk)
{
if (skb_queue_empty(queue))
return NULL;
if (!previous)
return skb_peek(queue);
if (skb_queue_is_last(queue, previous))
return NULL;
/* sk_data->skb stores the last scheduled packet for this subflow.
* If sk_data->skb was scheduled but not sent (e.g., due to nagle),
* we have to schedule it again.
*
* For the redundant scheduler, there are two cases:
* 1. sk_data->skb was not sent on another subflow:
* we have to schedule it again to ensure that we do not
* skip this packet.
* 2. sk_data->skb was already sent on another subflow:
* with regard to the redundant semantic, we have to
* schedule it again. However, we keep it simple and ignore it,
* as it was already sent by another subflow.
* This might be changed in the future.
*
* For case 1, send_head is equal previous, as only a single
* packet can be skipped.
*/
if (tcp_send_head(meta_sk) == previous)
return tcp_send_head(meta_sk);
return skb_queue_next(queue, previous);
}
static struct sk_buff *redundant_next_segment(struct sock *meta_sk,
int *reinject,
struct sock **subsk,
unsigned int *limit)
{
struct tcp_sock *meta_tp = tcp_sk(meta_sk);
struct mptcp_cb *mpcb = meta_tp->mpcb;
struct redsched_cb_data *cb_data = redsched_get_cb_data(meta_tp);
struct tcp_sock *first_tp = cb_data->next_subflow;
struct tcp_sock *tp;
struct sk_buff *skb;
int active_valid_sks = -1;
/* As we set it, we have to reset it as well. */
*limit = 0;
if (skb_queue_empty(&mpcb->reinject_queue) &&
skb_queue_empty(&meta_sk->sk_write_queue))
/* Nothing to send */
return NULL;
/* First try reinjections */
skb = skb_peek(&mpcb->reinject_queue);
if (skb) {
*subsk = get_available_subflow(meta_sk, skb, false);
if (!*subsk)
return NULL;
*reinject = 1;
return skb;
}
/* Then try indistinctly redundant and normal skbs */
if (!first_tp)
first_tp = mpcb->connection_list;
/* still NULL (no subflow in connection_list?) */
if (!first_tp)
return NULL;
tp = first_tp;
*reinject = 0;
active_valid_sks = redsched_get_active_valid_sks(meta_sk);
do {
struct redsched_sock_data *sk_data;
/* Correct the skb pointers of the current subflow */
sk_data = redsched_get_sock_data(tp);
redsched_correct_skb_pointers(meta_sk, sk_data);
skb = redundant_next_skb_from_queue(&meta_sk->sk_write_queue,
sk_data->skb, meta_sk);
if (skb && redsched_use_subflow(meta_sk, active_valid_sks, tp,
skb)) {
sk_data->skb = skb;
sk_data->skb_end_seq = TCP_SKB_CB(skb)->end_seq;
cb_data->next_subflow = tp->mptcp->next;
*subsk = (struct sock *)tp;
if (TCP_SKB_CB(skb)->path_mask)
*reinject = -1;
return skb;
}
tp = tp->mptcp->next;
if (!tp)
tp = mpcb->connection_list;
} while (tp != first_tp);
/* Nothing to send */
return NULL;
}
static void redundant_release(struct sock *sk)
{
struct tcp_sock *tp = tcp_sk(sk);
struct redsched_cb_data *cb_data = redsched_get_cb_data(tp);
/* Check if the next subflow would be the released one. If yes correct
* the pointer
*/
if (cb_data->next_subflow == tp)
cb_data->next_subflow = tp->mptcp->next;
}
static struct mptcp_sched_ops mptcp_sched_redundant = {
.get_subflow = redundant_get_subflow,
.next_segment = redundant_next_segment,
.release = redundant_release,
.name = "redundant",
.owner = THIS_MODULE,
};
static int __init redundant_register(void)
{
BUILD_BUG_ON(sizeof(struct redsched_sock_data) > MPTCP_SCHED_SIZE);
BUILD_BUG_ON(sizeof(struct redsched_cb_data) > MPTCP_SCHED_DATA_SIZE);
if (mptcp_register_scheduler(&mptcp_sched_redundant))
return -1;
return 0;
}
static void redundant_unregister(void)
{
mptcp_unregister_scheduler(&mptcp_sched_redundant);
}
module_init(redundant_register);
module_exit(redundant_unregister);
MODULE_AUTHOR("Tobias Erbshaeusser, Alexander Froemmgen");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("REDUNDANT MPTCP");
MODULE_VERSION("0.90");