Release 4.11 net/l2tp/l2tp_core.c
/*
* L2TP core.
*
* Copyright (c) 2008,2009,2010 Katalix Systems Ltd
*
* This file contains some code of the original L2TPv2 pppol2tp
* driver, which has the following copyright:
*
* Authors: Martijn van Oosterhout <kleptog@svana.org>
* James Chapman (jchapman@katalix.com)
* Contributors:
* Michal Ostrowski <mostrows@speakeasy.net>
* Arnaldo Carvalho de Melo <acme@xconectiva.com.br>
* David S. Miller (davem@redhat.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/string.h>
#include <linux/list.h>
#include <linux/rculist.h>
#include <linux/uaccess.h>
#include <linux/kernel.h>
#include <linux/spinlock.h>
#include <linux/kthread.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/jiffies.h>
#include <linux/netdevice.h>
#include <linux/net.h>
#include <linux/inetdevice.h>
#include <linux/skbuff.h>
#include <linux/init.h>
#include <linux/in.h>
#include <linux/ip.h>
#include <linux/udp.h>
#include <linux/l2tp.h>
#include <linux/hash.h>
#include <linux/sort.h>
#include <linux/file.h>
#include <linux/nsproxy.h>
#include <net/net_namespace.h>
#include <net/netns/generic.h>
#include <net/dst.h>
#include <net/ip.h>
#include <net/udp.h>
#include <net/udp_tunnel.h>
#include <net/inet_common.h>
#include <net/xfrm.h>
#include <net/protocol.h>
#include <net/inet6_connection_sock.h>
#include <net/inet_ecn.h>
#include <net/ip6_route.h>
#include <net/ip6_checksum.h>
#include <asm/byteorder.h>
#include <linux/atomic.h>
#include "l2tp_core.h"
#define L2TP_DRV_VERSION "V2.0"
/* L2TP header constants */
#define L2TP_HDRFLAG_T 0x8000
#define L2TP_HDRFLAG_L 0x4000
#define L2TP_HDRFLAG_S 0x0800
#define L2TP_HDRFLAG_O 0x0200
#define L2TP_HDRFLAG_P 0x0100
#define L2TP_HDR_VER_MASK 0x000F
#define L2TP_HDR_VER_2 0x0002
#define L2TP_HDR_VER_3 0x0003
/* L2TPv3 default L2-specific sublayer */
#define L2TP_SLFLAG_S 0x40000000
#define L2TP_SL_SEQ_MASK 0x00ffffff
#define L2TP_HDR_SIZE_SEQ 10
#define L2TP_HDR_SIZE_NOSEQ 6
/* Default trace flags */
#define L2TP_DEFAULT_DEBUG_FLAGS 0
/* Private data stored for received packets in the skb.
*/
struct l2tp_skb_cb {
u32 ns;
u16 has_seq;
u16 length;
unsigned long expires;
};
#define L2TP_SKB_CB(skb) ((struct l2tp_skb_cb *) &skb->cb[sizeof(struct inet_skb_parm)])
static atomic_t l2tp_tunnel_count;
static atomic_t l2tp_session_count;
static struct workqueue_struct *l2tp_wq;
/* per-net private data for this module */
static unsigned int l2tp_net_id;
struct l2tp_net {
struct list_head l2tp_tunnel_list;
spinlock_t l2tp_tunnel_list_lock;
struct hlist_head l2tp_session_hlist[L2TP_HASH_SIZE_2];
spinlock_t l2tp_session_hlist_lock;
};
static void l2tp_tunnel_free(struct l2tp_tunnel *tunnel);
static inline struct l2tp_tunnel *l2tp_tunnel(struct sock *sk)
{
return sk->sk_user_data;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
David S. Miller | 19 | 100.00% | 1 | 100.00% |
Total | 19 | 100.00% | 1 | 100.00% |
static inline struct l2tp_net *l2tp_pernet(struct net *net)
{
BUG_ON(!net);
return net_generic(net, l2tp_net_id);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
James Chapman | 28 | 100.00% | 1 | 100.00% |
Total | 28 | 100.00% | 1 | 100.00% |
/* Tunnel reference counts. Incremented per session that is added to
* the tunnel.
*/
static inline void l2tp_tunnel_inc_refcount_1(struct l2tp_tunnel *tunnel)
{
atomic_inc(&tunnel->ref_count);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Stephen Hemminger | 20 | 100.00% | 1 | 100.00% |
Total | 20 | 100.00% | 1 | 100.00% |
static inline void l2tp_tunnel_dec_refcount_1(struct l2tp_tunnel *tunnel)
{
if (atomic_dec_and_test(&tunnel->ref_count))
l2tp_tunnel_free(tunnel);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Stephen Hemminger | 27 | 100.00% | 1 | 100.00% |
Total | 27 | 100.00% | 1 | 100.00% |
#ifdef L2TP_REFCNT_DEBUG
#define l2tp_tunnel_inc_refcount(_t) \
do { \
pr_debug("l2tp_tunnel_inc_refcount: %s:%d %s: cnt=%d\n", \
__func__, __LINE__, (_t)->name, \
atomic_read(&_t->ref_count)); \
l2tp_tunnel_inc_refcount_1(_t); \
} while (0)
#define l2tp_tunnel_dec_refcount(_t) \
do { \
pr_debug("l2tp_tunnel_dec_refcount: %s:%d %s: cnt=%d\n", \
__func__, __LINE__, (_t)->name, \
atomic_read(&_t->ref_count)); \
l2tp_tunnel_dec_refcount_1(_t); \
} while (0)
#else
#define l2tp_tunnel_inc_refcount(t) l2tp_tunnel_inc_refcount_1(t)
#define l2tp_tunnel_dec_refcount(t) l2tp_tunnel_dec_refcount_1(t)
#endif
/* Session hash global list for L2TPv3.
* The session_id SHOULD be random according to RFC3931, but several
* L2TP implementations use incrementing session_ids. So we do a real
* hash on the session_id, rather than a simple bitmask.
*/
static inline struct hlist_head *
l2tp_session_id_hash_2(struct l2tp_net *pn, u32 session_id)
{
return &pn->l2tp_session_hlist[hash_32(session_id, L2TP_HASH_BITS_2)];
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
James Chapman | 25 | 80.65% | 1 | 50.00% |
Andy Zhou | 6 | 19.35% | 1 | 50.00% |
Total | 31 | 100.00% | 2 | 100.00% |
/* Lookup the tunnel socket, possibly involving the fs code if the socket is
* owned by userspace. A struct sock returned from this function must be
* released using l2tp_tunnel_sock_put once you're done with it.
*/
static struct sock *l2tp_tunnel_sock_lookup(struct l2tp_tunnel *tunnel)
{
int err = 0;
struct socket *sock = NULL;
struct sock *sk = NULL;
if (!tunnel)
goto out;
if (tunnel->fd >= 0) {
/* Socket is owned by userspace, who might be in the process
* of closing it. Look the socket up using the fd to ensure
* consistency.
*/
sock = sockfd_lookup(tunnel->fd, &err);
if (sock)
sk = sock->sk;
} else {
/* Socket is owned by kernelspace */
sk = tunnel->sock;
sock_hold(sk);
}
out:
return sk;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Tom Parkin | 92 | 98.92% | 2 | 66.67% |
Stephen Hemminger | 1 | 1.08% | 1 | 33.33% |
Total | 93 | 100.00% | 3 | 100.00% |
/* Drop a reference to a tunnel socket obtained via. l2tp_tunnel_sock_put */
static void l2tp_tunnel_sock_put(struct sock *sk)
{
struct l2tp_tunnel *tunnel = l2tp_sock_to_tunnel(sk);
if (tunnel) {
if (tunnel->fd >= 0) {
/* Socket is owned by userspace */
sockfd_put(sk->sk_socket);
}
sock_put(sk);
}
sock_put(sk);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Tom Parkin | 54 | 98.18% | 2 | 66.67% |
Stephen Hemminger | 1 | 1.82% | 1 | 33.33% |
Total | 55 | 100.00% | 3 | 100.00% |
/* Lookup a session by id in the global session list
*/
static struct l2tp_session *l2tp_session_find_2(struct net *net, u32 session_id)
{
struct l2tp_net *pn = l2tp_pernet(net);
struct hlist_head *session_list =
l2tp_session_id_hash_2(pn, session_id);
struct l2tp_session *session;
rcu_read_lock_bh();
hlist_for_each_entry_rcu(session, session_list, global_hlist) {
if (session->session_id == session_id) {
rcu_read_unlock_bh();
return session;
}
}
rcu_read_unlock_bh();
return NULL;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
James Chapman | 78 | 100.00% | 2 | 100.00% |
Total | 78 | 100.00% | 2 | 100.00% |
/* Session hash list.
* The session_id SHOULD be random according to RFC2661, but several
* L2TP implementations (Cisco and Microsoft) use incrementing
* session_ids. So we do a real hash on the session_id, rather than a
* simple bitmask.
*/
static inline struct hlist_head *
l2tp_session_id_hash(struct l2tp_tunnel *tunnel, u32 session_id)
{
return &tunnel->session_hlist[hash_32(session_id, L2TP_HASH_BITS)];
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
James Chapman | 31 | 100.00% | 1 | 100.00% |
Total | 31 | 100.00% | 1 | 100.00% |
/* Lookup a session by id
*/
struct l2tp_session *l2tp_session_find(struct net *net, struct l2tp_tunnel *tunnel, u32 session_id)
{
struct hlist_head *session_list;
struct l2tp_session *session;
/* In L2TPv3, session_ids are unique over all tunnels and we
* sometimes need to look them up before we know the
* tunnel.
*/
if (tunnel == NULL)
return l2tp_session_find_2(net, session_id);
session_list = l2tp_session_id_hash(tunnel, session_id);
read_lock_bh(&tunnel->hlist_lock);
hlist_for_each_entry(session, session_list, hlist) {
if (session->session_id == session_id) {
read_unlock_bh(&tunnel->hlist_lock);
return session;
}
}
read_unlock_bh(&tunnel->hlist_lock);
return NULL;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
James Chapman | 104 | 100.00% | 2 | 100.00% |
Total | 104 | 100.00% | 2 | 100.00% |
EXPORT_SYMBOL_GPL(l2tp_session_find);
/* Like l2tp_session_find() but takes a reference on the returned session.
* Optionally calls session->ref() too if do_ref is true.
*/
struct l2tp_session *l2tp_session_get(struct net *net,
struct l2tp_tunnel *tunnel,
u32 session_id, bool do_ref)
{
struct hlist_head *session_list;
struct l2tp_session *session;
if (!tunnel) {
struct l2tp_net *pn = l2tp_pernet(net);
session_list = l2tp_session_id_hash_2(pn, session_id);
rcu_read_lock_bh();
hlist_for_each_entry_rcu(session, session_list, global_hlist) {
if (session->session_id == session_id) {
l2tp_session_inc_refcount(session);
if (do_ref && session->ref)
session->ref(session);
rcu_read_unlock_bh();
return session;
}
}
rcu_read_unlock_bh();
return NULL;
}
session_list = l2tp_session_id_hash(tunnel, session_id);
read_lock_bh(&tunnel->hlist_lock);
hlist_for_each_entry(session, session_list, hlist) {
if (session->session_id == session_id) {
l2tp_session_inc_refcount(session);
if (do_ref && session->ref)
session->ref(session);
read_unlock_bh(&tunnel->hlist_lock);
return session;
}
}
read_unlock_bh(&tunnel->hlist_lock);
return NULL;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Guillaume Nault | 193 | 100.00% | 1 | 100.00% |
Total | 193 | 100.00% | 1 | 100.00% |
EXPORT_SYMBOL_GPL(l2tp_session_get);
struct l2tp_session *l2tp_session_get_nth(struct l2tp_tunnel *tunnel, int nth,
bool do_ref)
{
int hash;
struct l2tp_session *session;
int count = 0;
read_lock_bh(&tunnel->hlist_lock);
for (hash = 0; hash < L2TP_HASH_SIZE; hash++) {
hlist_for_each_entry(session, &tunnel->session_hlist[hash], hlist) {
if (++count > nth) {
l2tp_session_inc_refcount(session);
if (do_ref && session->ref)
session->ref(session);
read_unlock_bh(&tunnel->hlist_lock);
return session;
}
}
}
read_unlock_bh(&tunnel->hlist_lock);
return NULL;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
James Chapman | 91 | 79.13% | 1 | 50.00% |
Guillaume Nault | 24 | 20.87% | 1 | 50.00% |
Total | 115 | 100.00% | 2 | 100.00% |
EXPORT_SYMBOL_GPL(l2tp_session_get_nth);
/* Lookup a session by interface name.
* This is very inefficient but is only used by management interfaces.
*/
struct l2tp_session *l2tp_session_get_by_ifname(struct net *net, char *ifname,
bool do_ref)
{
struct l2tp_net *pn = l2tp_pernet(net);
int hash;
struct l2tp_session *session;
rcu_read_lock_bh();
for (hash = 0; hash < L2TP_HASH_SIZE_2; hash++) {
hlist_for_each_entry_rcu(session, &pn->l2tp_session_hlist[hash], global_hlist) {
if (!strcmp(session->ifname, ifname)) {
l2tp_session_inc_refcount(session);
if (do_ref && session->ref)
session->ref(session);
rcu_read_unlock_bh();
return session;
}
}
}
rcu_read_unlock_bh();
return NULL;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
James Chapman | 87 | 78.38% | 2 | 66.67% |
Guillaume Nault | 24 | 21.62% | 1 | 33.33% |
Total | 111 | 100.00% | 3 | 100.00% |
EXPORT_SYMBOL_GPL(l2tp_session_get_by_ifname);
static int l2tp_session_add_to_tunnel(struct l2tp_tunnel *tunnel,
struct l2tp_session *session)
{
struct l2tp_session *session_walk;
struct hlist_head *g_head;
struct hlist_head *head;
struct l2tp_net *pn;
head = l2tp_session_id_hash(tunnel, session->session_id);
write_lock_bh(&tunnel->hlist_lock);
hlist_for_each_entry(session_walk, head, hlist)
if (session_walk->session_id == session->session_id)
goto exist;
if (tunnel->version == L2TP_HDR_VER_3) {
pn = l2tp_pernet(tunnel->l2tp_net);
g_head = l2tp_session_id_hash_2(l2tp_pernet(tunnel->l2tp_net),
session->session_id);
spin_lock_bh(&pn->l2tp_session_hlist_lock);
hlist_for_each_entry(session_walk, g_head, global_hlist)
if (session_walk->session_id == session->session_id)
goto exist_glob;
hlist_add_head_rcu(&session->global_hlist, g_head);
spin_unlock_bh(&pn->l2tp_session_hlist_lock);
}
hlist_add_head(&session->hlist, head);
write_unlock_bh(&tunnel->hlist_lock);
return 0;
exist_glob:
spin_unlock_bh(&pn->l2tp_session_hlist_lock);
exist:
write_unlock_bh(&tunnel->hlist_lock);
return -EEXIST;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Guillaume Nault | 203 | 100.00% | 1 | 100.00% |
Total | 203 | 100.00% | 1 | 100.00% |
/* Lookup a tunnel by id
*/
struct l2tp_tunnel *l2tp_tunnel_find(struct net *net, u32 tunnel_id)
{
struct l2tp_tunnel *tunnel;
struct l2tp_net *pn = l2tp_pernet(net);
rcu_read_lock_bh();
list_for_each_entry_rcu(tunnel, &pn->l2tp_tunnel_list, list) {
if (tunnel->tunnel_id == tunnel_id) {
rcu_read_unlock_bh();
return tunnel;
}
}
rcu_read_unlock_bh();
return NULL;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
James Chapman | 65 | 100.00% | 2 | 100.00% |
Total | 65 | 100.00% | 2 | 100.00% |
EXPORT_SYMBOL_GPL(l2tp_tunnel_find);
struct l2tp_tunnel *l2tp_tunnel_find_nth(struct net *net, int nth)
{
struct l2tp_net *pn = l2tp_pernet(net);
struct l2tp_tunnel *tunnel;
int count = 0;
rcu_read_lock_bh();
list_for_each_entry_rcu(tunnel, &pn->l2tp_tunnel_list, list) {
if (++count > nth) {
rcu_read_unlock_bh();
return tunnel;
}
}
rcu_read_unlock_bh();
return NULL;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
James Chapman | 69 | 100.00% | 2 | 100.00% |
Total | 69 | 100.00% | 2 | 100.00% |
EXPORT_SYMBOL_GPL(l2tp_tunnel_find_nth);
/*****************************************************************************
* Receive data handling
*****************************************************************************/
/* Queue a skb in order. We come here only if the skb has an L2TP sequence
* number.
*/
static void l2tp_recv_queue_skb(struct l2tp_session *session, struct sk_buff *skb)
{
struct sk_buff *skbp;
struct sk_buff *tmp;
u32 ns = L2TP_SKB_CB(skb)->ns;
spin_lock_bh(&session->reorder_q.lock);
skb_queue_walk_safe(&session->reorder_q, skbp, tmp) {
if (L2TP_SKB_CB(skbp)->ns > ns) {
__skb_queue_before(&session->reorder_q, skbp, skb);
l2tp_dbg(session, L2TP_MSG_SEQ,
"%s: pkt %hu, inserted before %hu, reorder_q len=%d\n",
session->name, ns, L2TP_SKB_CB(skbp)->ns,
skb_queue_len(&session->reorder_q));
atomic_long_inc(&session->stats.rx_oos_packets);
goto out;
}
}
__skb_queue_tail(&session->reorder_q, skb);
out:
spin_unlock_bh(&session->reorder_q.lock);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
James Chapman | 141 | 96.58% | 3 | 60.00% |
Tom Parkin | 4 | 2.74% | 1 | 20.00% |
Joe Perches | 1 | 0.68% | 1 | 20.00% |
Total | 146 | 100.00% | 5 | 100.00% |
/* Dequeue a single skb.
*/
static void l2tp_recv_dequeue_skb(struct l2tp_session *session, struct sk_buff *skb)
{
struct l2tp_tunnel *tunnel = session->tunnel;
int length = L2TP_SKB_CB(skb)->length;
/* We're about to requeue the skb, so return resources
* to its current owner (a socket receive buffer).
*/
skb_orphan(skb);
atomic_long_inc(&tunnel->stats.rx_packets);
atomic_long_add(length, &tunnel->stats.rx_bytes);
atomic_long_inc(&session->stats.rx_packets);
atomic_long_add(length, &session->stats.rx_bytes);
if (L2TP_SKB_CB(skb)->has_seq) {
/* Bump our Nr */
session->nr++;
session->nr &= session->nr_max;
l2tp_dbg(session, L2TP_MSG_SEQ, "%s: updated nr to %hu\n",
session->name, session->nr);
}
/* call private receive handler */
if (session->recv_skb != NULL)
(*session->recv_skb)(session, skb, L2TP_SKB_CB(skb)->length);
else
kfree_skb(skb);
if (session->deref)
(*session->deref)(session);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
James Chapman | 154 | 87.01% | 4 | 66.67% |
Tom Parkin | 22 | 12.43% | 1 | 16.67% |
Joe Perches | 1 | 0.56% | 1 | 16.67% |
Total | 177 | 100.00% | 6 | 100.00% |
/* Dequeue skbs from the session's reorder_q, subject to packet order.
* Skbs that have been in the queue for too long are simply discarded.
*/
static void l2tp_recv_dequeue(struct l2tp_session *session)
{
struct sk_buff *skb;
struct sk_buff *tmp;
/* If the pkt at the head of the queue has the nr that we
* expect to send up next, dequeue it and any other
* in-sequence packets behind it.
*/
start:
spin_lock_bh(&session->reorder_q.lock);
skb_queue_walk_safe(&session->reorder_q, skb, tmp) {
if (time_after(jiffies, L2TP_SKB_CB(skb)->expires)) {
atomic_long_inc(&session->stats.rx_seq_discards);
atomic_long_inc(&session->stats.rx_errors);
l2tp_dbg(session, L2TP_MSG_SEQ,
"%s: oos pkt %u len %d discarded (too old), 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));
session->reorder_skip = 1;
__skb_unlink(skb, &session->reorder_q);
kfree_skb(skb);
if (session->deref)
(*session->deref)(session);
continue;
}
if (L2TP_SKB_CB(skb)->has_seq) {
if (session->reorder_skip) {
l2tp_dbg(session, L2TP_MSG_SEQ,
"%s: advancing nr to next pkt: %u -> %u",
session->name, session->nr,
L2TP_SKB_CB(skb)->ns);
session->reorder_skip = 0;
session->nr = L2TP_SKB_CB(skb)->ns;
}
if (L2TP_SKB_CB(skb)->ns != session->nr) {
l2tp_dbg(session, L2TP_MSG_SEQ,
"%s: holding oos pkt %u len %d, 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 out;
}
}
__skb_unlink(skb, &session->reorder_q);
/* Process the skb. We release the queue lock while we
* do so to let other contexts process the queue.
*/
spin_unlock_bh(&session->reorder_q.lock);
l2tp_recv_dequeue_skb(session, skb);
goto start;
}
out:
spin_unlock_bh(&session->reorder_q.lock);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
James Chapman | 297 | 93.69% | 3 | 50.00% |
Tom Parkin | 10 | 3.15% | 1 | 16.67% |
Joe Perches | 5 | 1.58% | 1 | 16.67% |
Eric Dumazet | 5 | 1.58% | 1 | 16.67% |
Total | 317 | 100.00% | 6 | 100.00% |
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;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
James Chapman | 59 | 100.00% | 1 | 100.00% |
Total | 59 | 100.00% | 1 | 100.00% |
/* 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;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
James Chapman | 283 | 100.00% | 3 | 100.00% |
Total | 283 | 100.00% | 3 | 100.00% |
/* Do receive processing of L2TP data frames. We handle both L2TPv2
* and L2TPv3 data frames here.
*
* L2TPv2 Data Message Header
*
* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* |T|L|x|x|S|x|O|P|x|x|x|x| Ver | Length (opt) |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Tunnel ID | Session ID |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Ns (opt) | Nr (opt) |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Offset Size (opt) | Offset pad... (opt)
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*
* Data frames are marked by T=0. All other fields are the same as
* those in L2TP control frames.
*
* L2TPv3 Data Message Header
*
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | L2TP Session Header |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | L2-Specific Sublayer |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Tunnel Payload ...
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*
* L2TPv3 Session Header Over IP
*
* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Session ID |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Cookie (optional, maximum 64 bits)...
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*
* L2TPv3 L2-Specific Sublayer Format
*
* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* |x|S|x|x|x|x|x|x| Sequence Number |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*
* Cookie value, sublayer format and offset (pad) are negotiated with
* the peer when the session is set up. Unlike L2TPv2, we do not need
* to parse the packet header to determine if optional fields are
* present.
*
* Caller must already have parsed the frame and determined that it is
* a data (not control) frame before coming here. Fields up to the
* session-id have already been parsed and ptr points to the data
* after the session-id.
*
* session->ref() must have been called prior to l2tp_recv_common().
* session->deref() will be called automatically after skb is processed.
*/
void l2tp_recv_common(struct l2tp_session *session, struct sk_buff *skb,
unsigned char *ptr, unsigned char *optr, u16 hdrflags,
int length, int (*payload_hook)(struct sk_buff *skb))
{
struct l2tp_tunnel *tunnel = session->tunnel;
int offset;
u32 ns, nr;
/* Parse and check optional cookie */
if (session->peer_cookie_len > 0) {
if (memcmp(ptr, &session->peer_cookie[0], session->peer_cookie_len)) {
l2tp_info(tunnel, L2TP_MSG_DATA,
"%s: cookie mismatch (%u/%u). Discarding.\n",
tunnel->name, tunnel->tunnel_id,
session->session_id);
atomic_long_inc(&session->stats.rx_cookie_discards);
goto discard;
}
ptr += session->peer_cookie_len;
}
/* Handle the optional sequence numbers. Sequence numbers are
* in different places for L2TPv2 and L2TPv3.
*
* If we are the LAC, enable/disable sequence numbers under
* the control of the LNS. If no sequence numbers present but
* we were expecting them, discard frame.
*/
ns = nr = 0;
L2TP_SKB_CB(skb)->has_seq = 0;
if (tunnel->version == L2TP_HDR_VER_2) {
if (hdrflags & L2TP_HDRFLAG_S) {
ns = ntohs(*(__be16 *) ptr);
ptr += 2;
nr = ntohs(*(__be16 *) ptr);
ptr += 2;
/* Store L2TP info in the skb */
L2TP_SKB_CB(skb)->ns = ns;
L2TP_SKB_CB(skb)->has_seq = 1;
l2tp_dbg(session, L2TP_MSG_SEQ,
"%s: recv data ns=%u, nr=%u, session nr=%u\n",
session->name, ns, nr, session->nr);
}
} else if (session->l2specific_type == L2TP_L2SPECTYPE_DEFAULT) {
u32 l2h = ntohl(*(__be32 *) ptr);
if (l2h & 0x40000000) {
ns = l2h & 0x00ffffff;
/* Store L2TP info in the skb */
L2TP_SKB_CB(skb)->ns = ns;
L2TP_SKB_CB(skb)->has_seq = 1;
l2tp_dbg(session, L2TP_MSG_SEQ,
"%s: recv data ns=%u, session nr=%u\n",
session->name, ns, session->nr);
}
}
/* Advance past L2-specific header, if present */
ptr += session->l2specific_len;
if (L2TP_SKB_CB(skb)->has_seq) {
/* Received a packet with sequence numbers. If we're the LNS,
* check if we sre sending sequence numbers and if not,
* configure it so.
*/
if ((!session->lns_mode) && (!session->send_seq)) {
l2tp_info(session, L2TP_MSG_SEQ,
"%s: requested to enable seq numbers by LNS\n",
session->name);
session->send_seq = 1