cregit-Linux how code gets into the kernel

Release 4.8 net/l2tp/l2tp_core.c

Directory: net/l2tp
/*
 * 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

PersonTokensPropCommitsCommitProp
david s. millerdavid s. miller19100.00%1100.00%
Total19100.00%1100.00%


static inline struct l2tp_net *l2tp_pernet(struct net *net) { BUG_ON(!net); return net_generic(net, l2tp_net_id); }

Contributors

PersonTokensPropCommitsCommitProp
james chapmanjames chapman28100.00%1100.00%
Total28100.00%1100.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

PersonTokensPropCommitsCommitProp
stephen hemmingerstephen hemminger20100.00%1100.00%
Total20100.00%1100.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

PersonTokensPropCommitsCommitProp
stephen hemmingerstephen hemminger27100.00%1100.00%
Total27100.00%1100.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

PersonTokensPropCommitsCommitProp
james chapmanjames chapman2580.65%150.00%
andy zhouandy zhou619.35%150.00%
Total31100.00%2100.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

PersonTokensPropCommitsCommitProp
tom parkintom parkin9298.92%266.67%
stephen hemmingerstephen hemminger11.08%133.33%
Total93100.00%3100.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

PersonTokensPropCommitsCommitProp
tom parkintom parkin5498.18%266.67%
stephen hemmingerstephen hemminger11.82%133.33%
Total55100.00%3100.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

PersonTokensPropCommitsCommitProp
james chapmanjames chapman78100.00%2100.00%
Total78100.00%2100.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

PersonTokensPropCommitsCommitProp
james chapmanjames chapman31100.00%1100.00%
Total31100.00%1100.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

PersonTokensPropCommitsCommitProp
james chapmanjames chapman104100.00%2100.00%
Total104100.00%2100.00%

EXPORT_SYMBOL_GPL(l2tp_session_find);
struct l2tp_session *l2tp_session_find_nth(struct l2tp_tunnel *tunnel, int nth) { 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) { read_unlock_bh(&tunnel->hlist_lock); return session; } } } read_unlock_bh(&tunnel->hlist_lock); return NULL; }

Contributors

PersonTokensPropCommitsCommitProp
james chapmanjames chapman92100.00%1100.00%
Total92100.00%1100.00%

EXPORT_SYMBOL_GPL(l2tp_session_find_nth); /* Lookup a session by interface name. * This is very inefficient but is only used by management interfaces. */
struct l2tp_session *l2tp_session_find_by_ifname(struct net *net, char *ifname) { 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)) { rcu_read_unlock_bh(); return session; } } } rcu_read_unlock_bh(); return NULL; }

Contributors

PersonTokensPropCommitsCommitProp
james chapmanjames chapman88100.00%2100.00%
Total88100.00%2100.00%

EXPORT_SYMBOL_GPL(l2tp_session_find_by_ifname); /* 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

PersonTokensPropCommitsCommitProp
james chapmanjames chapman65100.00%2100.00%
Total65100.00%2100.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

PersonTokensPropCommitsCommitProp
james chapmanjames chapman69100.00%2100.00%
Total69100.00%2100.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

PersonTokensPropCommitsCommitProp
james chapmanjames chapman14196.58%360.00%
tom parkintom parkin42.74%120.00%
joe perchesjoe perches10.68%120.00%
Total146100.00%5100.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

PersonTokensPropCommitsCommitProp
james chapmanjames chapman15487.01%466.67%
tom parkintom parkin2212.43%116.67%
joe perchesjoe perches10.56%116.67%
Total177100.00%6100.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

PersonTokensPropCommitsCommitProp
james chapmanjames chapman29793.69%350.00%
tom parkintom parkin103.15%116.67%
eric dumazeteric dumazet51.58%116.67%
joe perchesjoe perches51.58%116.67%
Total317100.00%6100.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

PersonTokensPropCommitsCommitProp
james chapmanjames chapman59100.00%1100.00%
Total59100.00%1100.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

PersonTokensPropCommitsCommitProp
james chapmanjames chapman283100.00%3100.00%
Total283100.00%3100.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. */
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; /* The ref count is increased since we now hold a pointer to * the session. Take care to decrement the refcnt when exiting * this function from now on... */ l2tp_session_inc_refcount(session); if (session->ref) (*session->ref)(session); /* 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; l2tp_session_set_header_len(session, tunnel->version); } } else { /* No sequence numbers. * If user has configured mandatory sequence numbers, discard. */ if (session->recv_seq) { l2tp_warn(session, L2TP_MSG_SEQ, "%s: recv data has no seq numbers when required. Discarding.\n", session->name); atomic_long_inc(&session->stats.rx_seq_discards); goto discard; } /* If we're the LAC and we're sending sequence numbers, the * LNS has requested that we no longer send sequence numbers. * If we're the LNS and we're sending sequence numbers, the * LAC is broken. Discard the frame. */ if ((!session->lns_mode) && (session->send_seq)) { l2tp_info(session, L2TP_MSG_SEQ, "%s: requested to disable seq numbers by LNS\n", session->name); session->send_seq = 0; l2tp_session_set_header_len(session, tunnel->version); } else if (session->send_seq) { l2tp_warn(session, L2TP_MSG_SEQ, "%s: recv data has no seq numbers when required. Discarding.\n", session->name); atomic_long_inc(&session->stats.rx_seq_discards); goto discard; } } /* Session data offset is handled differently for L2TPv2 and * L2TPv3. For L2TPv2, there is an optional 16-bit value in * the header. For L2TPv3, the offset is negotiated using AVPs * in the session setup control protocol. */ if (tunnel->version == L2TP_HDR_VER_2) { /* If offset bit set, skip it. */ if (hdrflags & L2TP_HDRFLAG_O) { offset = ntohs(*(__be16 *)ptr); ptr += 2 + offset; } } else ptr += session->offset; offset = ptr - optr; if (!pskb_may_pull(skb, offset)) goto discard; __skb_pull(skb, offset); /* If caller wants to process the payload before we queue the * packet, do so now. */ if (payload_hook) if ((*payload_hook)(skb)) goto discard; /* Prepare skb for adding to the session's reorder_q. Hold * packets for max reorder_timeout or 1 second if not * reordering. */ L2TP_SKB_CB(skb)->length = length; L2TP_SKB_CB(skb)->expires = jiffies + (session->reorder_timeout ? session->reorder_timeout : HZ); /* Add packet to the session's receive queue. Reordering is done here, if * enabled. Saved L2TP protocol info is stored in skb->sb[]. */ if (L2TP_SKB_CB(skb)->has_seq) { 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 * delivered after all previous sequenced skbs. */ skb_queue_tail(&session->reorder_q, skb); } /* Try to dequeue as many skbs from reorder_q as we can. */ l2tp_recv_dequeue(session); l2tp_session_dec_refcount(session); return; discard: atomic_long_inc(&session->stats.rx_errors); kfree_skb(skb); if (session->deref)