Skip to content

Commit

Permalink
Merge branch 'l2tp_seq'
Browse files Browse the repository at this point in the history
James Chapman says:

====================
L2TP data sequence numbers, if enabled, ensure in-order delivery. A
receiver may reorder data packets, or simply drop out-of-sequence
packets. If reordering is not enabled, the current implementation does
not handle data packet loss correctly, which can result in a stalled
L2TP session datapath as soon as the first packet is lost. Most L2TP
users either disable sequence numbers or enable data packet reordering
when sequence numbers are used to circumvent the issue. This patch
series fixes the problem, and makes the L2TP sequence number handling
RFC-compliant.

v2 incorporates string format changes requested by sergei.shtylyov@cogentembedded.com.
====================

Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
David S. Miller committed Jul 2, 2013
2 parents 8a59bd3 + a0dbd82 commit 784771e
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 24 deletions.
114 changes: 90 additions & 24 deletions net/l2tp/l2tp_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -414,10 +414,7 @@ static void l2tp_recv_dequeue_skb(struct l2tp_session *session, struct sk_buff *
if (L2TP_SKB_CB(skb)->has_seq) {
/* Bump our Nr */
session->nr++;
if (tunnel->version == L2TP_HDR_VER_2)
session->nr &= 0xffff;
else
session->nr &= 0xffffff;
session->nr &= session->nr_max;

l2tp_dbg(session, L2TP_MSG_SEQ, "%s: updated nr to %hu\n",
session->name, session->nr);
Expand Down Expand Up @@ -542,6 +539,84 @@ static inline int l2tp_verify_udp_checksum(struct sock *sk,
return __skb_checksum_complete(skb);
}

static int l2tp_seq_check_rx_window(struct l2tp_session *session, u32 nr)
{
u32 nws;

if (nr >= session->nr)
nws = nr - session->nr;
else
nws = (session->nr_max + 1) - (session->nr - nr);

return nws < session->nr_window_size;
}

/* If packet has sequence numbers, queue it if acceptable. Returns 0 if
* acceptable, else non-zero.
*/
static int l2tp_recv_data_seq(struct l2tp_session *session, struct sk_buff *skb)
{
if (!l2tp_seq_check_rx_window(session, L2TP_SKB_CB(skb)->ns)) {
/* Packet sequence number is outside allowed window.
* Discard it.
*/
l2tp_dbg(session, L2TP_MSG_SEQ,
"%s: pkt %u len %d discarded, outside window, nr=%u\n",
session->name, L2TP_SKB_CB(skb)->ns,
L2TP_SKB_CB(skb)->length, session->nr);
goto discard;
}

if (session->reorder_timeout != 0) {
/* Packet reordering enabled. Add skb to session's
* reorder queue, in order of ns.
*/
l2tp_recv_queue_skb(session, skb);
goto out;
}

/* Packet reordering disabled. Discard out-of-sequence packets, while
* tracking the number if in-sequence packets after the first OOS packet
* is seen. After nr_oos_count_max in-sequence packets, reset the
* sequence number to re-enable packet reception.
*/
if (L2TP_SKB_CB(skb)->ns == session->nr) {
skb_queue_tail(&session->reorder_q, skb);
} else {
u32 nr_oos = L2TP_SKB_CB(skb)->ns;
u32 nr_next = (session->nr_oos + 1) & session->nr_max;

if (nr_oos == nr_next)
session->nr_oos_count++;
else
session->nr_oos_count = 0;

session->nr_oos = nr_oos;
if (session->nr_oos_count > session->nr_oos_count_max) {
session->reorder_skip = 1;
l2tp_dbg(session, L2TP_MSG_SEQ,
"%s: %d oos packets received. Resetting sequence numbers\n",
session->name, session->nr_oos_count);
}
if (!session->reorder_skip) {
atomic_long_inc(&session->stats.rx_seq_discards);
l2tp_dbg(session, L2TP_MSG_SEQ,
"%s: oos pkt %u len %d discarded, waiting for %u, reorder_q_len=%d\n",
session->name, L2TP_SKB_CB(skb)->ns,
L2TP_SKB_CB(skb)->length, session->nr,
skb_queue_len(&session->reorder_q));
goto discard;
}
skb_queue_tail(&session->reorder_q, skb);
}

out:
return 0;

discard:
return 1;
}

/* Do receive processing of L2TP data frames. We handle both L2TPv2
* and L2TPv3 data frames here.
*
Expand Down Expand Up @@ -757,26 +832,8 @@ void l2tp_recv_common(struct l2tp_session *session, struct sk_buff *skb,
* enabled. Saved L2TP protocol info is stored in skb->sb[].
*/
if (L2TP_SKB_CB(skb)->has_seq) {
if (session->reorder_timeout != 0) {
/* Packet reordering enabled. Add skb to session's
* reorder queue, in order of ns.
*/
l2tp_recv_queue_skb(session, skb);
} else {
/* Packet reordering disabled. Discard out-of-sequence
* packets
*/
if (L2TP_SKB_CB(skb)->ns != session->nr) {
atomic_long_inc(&session->stats.rx_seq_discards);
l2tp_dbg(session, L2TP_MSG_SEQ,
"%s: oos pkt %u len %d discarded, waiting for %u, reorder_q_len=%d\n",
session->name, L2TP_SKB_CB(skb)->ns,
L2TP_SKB_CB(skb)->length, session->nr,
skb_queue_len(&session->reorder_q));
goto discard;
}
skb_queue_tail(&session->reorder_q, skb);
}
if (l2tp_recv_data_seq(session, skb))
goto discard;
} else {
/* No sequence numbers. Add the skb to the tail of the
* reorder queue. This ensures that it will be
Expand Down Expand Up @@ -1812,6 +1869,15 @@ struct l2tp_session *l2tp_session_create(int priv_size, struct l2tp_tunnel *tunn
session->session_id = session_id;
session->peer_session_id = peer_session_id;
session->nr = 0;
if (tunnel->version == L2TP_HDR_VER_2)
session->nr_max = 0xffff;
else
session->nr_max = 0xffffff;
session->nr_window_size = session->nr_max / 2;
session->nr_oos_count_max = 4;

/* Use NR of first received packet */
session->reorder_skip = 1;

sprintf(&session->name[0], "sess %u/%u",
tunnel->tunnel_id, session->session_id);
Expand Down
5 changes: 5 additions & 0 deletions net/l2tp/l2tp_core.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,11 @@ struct l2tp_session {
u32 nr; /* session NR state (receive) */
u32 ns; /* session NR state (send) */
struct sk_buff_head reorder_q; /* receive reorder queue */
u32 nr_max; /* max NR. Depends on tunnel */
u32 nr_window_size; /* NR window size */
u32 nr_oos; /* NR of last OOS packet */
int nr_oos_count; /* For OOS recovery */
int nr_oos_count_max;
struct hlist_node hlist; /* Hash list node */
atomic_t ref_count;

Expand Down

0 comments on commit 784771e

Please sign in to comment.