pppol2tp.c
来自「linux 内核源代码」· C语言 代码 · 共 2,472 行 · 第 1/5 页
C
2,472 行
/***************************************************************************** * Linux PPP over L2TP (PPPoX/PPPoL2TP) Sockets * * PPPoX --- Generic PPP encapsulation socket family * PPPoL2TP --- PPP over L2TP (RFC 2661) * * Version: 1.0.0 * * 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) * * License: * 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. * *//* This driver handles only L2TP data frames; control frames are handled by a * userspace application. * * To send data in an L2TP session, userspace opens a PPPoL2TP socket and * attaches it to a bound UDP socket with local tunnel_id / session_id and * peer tunnel_id / session_id set. Data can then be sent or received using * regular socket sendmsg() / recvmsg() calls. Kernel parameters of the socket * can be read or modified using ioctl() or [gs]etsockopt() calls. * * When a PPPoL2TP socket is connected with local and peer session_id values * zero, the socket is treated as a special tunnel management socket. * * Here's example userspace code to create a socket for sending/receiving data * over an L2TP session:- * * struct sockaddr_pppol2tp sax; * int fd; * int session_fd; * * fd = socket(AF_PPPOX, SOCK_DGRAM, PX_PROTO_OL2TP); * * sax.sa_family = AF_PPPOX; * sax.sa_protocol = PX_PROTO_OL2TP; * sax.pppol2tp.fd = tunnel_fd; // bound UDP socket * sax.pppol2tp.addr.sin_addr.s_addr = addr->sin_addr.s_addr; * sax.pppol2tp.addr.sin_port = addr->sin_port; * sax.pppol2tp.addr.sin_family = AF_INET; * sax.pppol2tp.s_tunnel = tunnel_id; * sax.pppol2tp.s_session = session_id; * sax.pppol2tp.d_tunnel = peer_tunnel_id; * sax.pppol2tp.d_session = peer_session_id; * * session_fd = connect(fd, (struct sockaddr *)&sax, sizeof(sax)); * * A pppd plugin that allows PPP traffic to be carried over L2TP using * this driver is available from the OpenL2TP project at * http://openl2tp.sourceforge.net. */#include <linux/module.h>#include <linux/version.h>#include <linux/string.h>#include <linux/list.h>#include <asm/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/ip.h>#include <linux/udp.h>#include <linux/if_pppox.h>#include <linux/if_pppol2tp.h>#include <net/sock.h>#include <linux/ppp_channel.h>#include <linux/ppp_defs.h>#include <linux/if_ppp.h>#include <linux/file.h>#include <linux/hash.h>#include <linux/sort.h>#include <linux/proc_fs.h>#include <net/net_namespace.h>#include <net/dst.h>#include <net/ip.h>#include <net/udp.h>#include <net/xfrm.h>#include <asm/byteorder.h>#include <asm/atomic.h>#define PPPOL2TP_DRV_VERSION "V1.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 0x0002/* Space for UDP, L2TP and PPP headers */#define PPPOL2TP_HEADER_OVERHEAD 40/* Just some random numbers */#define L2TP_TUNNEL_MAGIC 0x42114DDA#define L2TP_SESSION_MAGIC 0x0C04EB7D#define PPPOL2TP_HASH_BITS 4#define PPPOL2TP_HASH_SIZE (1 << PPPOL2TP_HASH_BITS)/* Default trace flags */#define PPPOL2TP_DEFAULT_DEBUG_FLAGS 0#define PRINTK(_mask, _type, _lvl, _fmt, args...) \ do { \ if ((_mask) & (_type)) \ printk(_lvl "PPPOL2TP: " _fmt, ##args); \ } while(0)/* Number of bytes to build transmit L2TP headers. * Unfortunately the size is different depending on whether sequence numbers * are enabled. */#define PPPOL2TP_L2TP_HDR_SIZE_SEQ 10#define PPPOL2TP_L2TP_HDR_SIZE_NOSEQ 6struct pppol2tp_tunnel;/* Describes a session. It is the sk_user_data field in the PPPoL2TP * socket. Contains information to determine incoming packets and transmit * outgoing ones. */struct pppol2tp_session{ int magic; /* should be * L2TP_SESSION_MAGIC */ int owner; /* pid that opened the socket */ struct sock *sock; /* Pointer to the session * PPPoX socket */ struct sock *tunnel_sock; /* Pointer to the tunnel UDP * socket */ struct pppol2tp_addr tunnel_addr; /* Description of tunnel */ struct pppol2tp_tunnel *tunnel; /* back pointer to tunnel * context */ char name[20]; /* "sess xxxxx/yyyyy", where * x=tunnel_id, y=session_id */ int mtu; int mru; int flags; /* accessed by PPPIOCGFLAGS. * Unused. */ unsigned recv_seq:1; /* expect receive packets with * sequence numbers? */ unsigned send_seq:1; /* send packets with sequence * numbers? */ unsigned lns_mode:1; /* behave as LNS? LAC enables * sequence numbers under * control of LNS. */ int debug; /* bitmask of debug message * categories */ int reorder_timeout; /* configured reorder timeout * (in jiffies) */ u16 nr; /* session NR state (receive) */ u16 ns; /* session NR state (send) */ struct sk_buff_head reorder_q; /* receive reorder queue */ struct pppol2tp_ioc_stats stats; struct hlist_node hlist; /* Hash list node */};/* The sk_user_data field of the tunnel's UDP socket. It contains info to track * all the associated sessions so incoming packets can be sorted out */struct pppol2tp_tunnel{ int magic; /* Should be L2TP_TUNNEL_MAGIC */ rwlock_t hlist_lock; /* protect session_hlist */ struct hlist_head session_hlist[PPPOL2TP_HASH_SIZE]; /* hashed list of sessions, * hashed by id */ int debug; /* bitmask of debug message * categories */ char name[12]; /* "tunl xxxxx" */ struct pppol2tp_ioc_stats stats; void (*old_sk_destruct)(struct sock *); struct sock *sock; /* Parent socket */ struct list_head list; /* Keep a list of all open * prepared sockets */ atomic_t ref_count;};/* Private data stored for received packets in the skb. */struct pppol2tp_skb_cb { u16 ns; u16 nr; u16 has_seq; u16 length; unsigned long expires;};#define PPPOL2TP_SKB_CB(skb) ((struct pppol2tp_skb_cb *) &skb->cb[sizeof(struct inet_skb_parm)])static int pppol2tp_xmit(struct ppp_channel *chan, struct sk_buff *skb);static void pppol2tp_tunnel_free(struct pppol2tp_tunnel *tunnel);static atomic_t pppol2tp_tunnel_count;static atomic_t pppol2tp_session_count;static struct ppp_channel_ops pppol2tp_chan_ops = { pppol2tp_xmit , NULL };static struct proto_ops pppol2tp_ops;static LIST_HEAD(pppol2tp_tunnel_list);static DEFINE_RWLOCK(pppol2tp_tunnel_list_lock);/* Helpers to obtain tunnel/session contexts from sockets. */static inline struct pppol2tp_session *pppol2tp_sock_to_session(struct sock *sk){ struct pppol2tp_session *session; if (sk == NULL) return NULL; session = (struct pppol2tp_session *)(sk->sk_user_data); if (session == NULL) return NULL; BUG_ON(session->magic != L2TP_SESSION_MAGIC); return session;}static inline struct pppol2tp_tunnel *pppol2tp_sock_to_tunnel(struct sock *sk){ struct pppol2tp_tunnel *tunnel; if (sk == NULL) return NULL; tunnel = (struct pppol2tp_tunnel *)(sk->sk_user_data); if (tunnel == NULL) return NULL; BUG_ON(tunnel->magic != L2TP_TUNNEL_MAGIC); return tunnel;}/* Tunnel reference counts. Incremented per session that is added to * the tunnel. */static inline void pppol2tp_tunnel_inc_refcount(struct pppol2tp_tunnel *tunnel){ atomic_inc(&tunnel->ref_count);}static inline void pppol2tp_tunnel_dec_refcount(struct pppol2tp_tunnel *tunnel){ if (atomic_dec_and_test(&tunnel->ref_count)) pppol2tp_tunnel_free(tunnel);}/* 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 *pppol2tp_session_id_hash(struct pppol2tp_tunnel *tunnel, u16 session_id){ unsigned long hash_val = (unsigned long) session_id; return &tunnel->session_hlist[hash_long(hash_val, PPPOL2TP_HASH_BITS)];}/* Lookup a session by id */static struct pppol2tp_session *pppol2tp_session_find(struct pppol2tp_tunnel *tunnel, u16 session_id){ struct hlist_head *session_list = pppol2tp_session_id_hash(tunnel, session_id); struct pppol2tp_session *session; struct hlist_node *walk; read_lock(&tunnel->hlist_lock); hlist_for_each_entry(session, walk, session_list, hlist) { if (session->tunnel_addr.s_session == session_id) { read_unlock(&tunnel->hlist_lock); return session; } } read_unlock(&tunnel->hlist_lock); return NULL;}/* Lookup a tunnel by id */static struct pppol2tp_tunnel *pppol2tp_tunnel_find(u16 tunnel_id){ struct pppol2tp_tunnel *tunnel = NULL; read_lock(&pppol2tp_tunnel_list_lock); list_for_each_entry(tunnel, &pppol2tp_tunnel_list, list) { if (tunnel->stats.tunnel_id == tunnel_id) { read_unlock(&pppol2tp_tunnel_list_lock); return tunnel; } } read_unlock(&pppol2tp_tunnel_list_lock); return NULL;}/***************************************************************************** * Receive data handling *****************************************************************************//* Queue a skb in order. We come here only if the skb has an L2TP sequence * number. */static void pppol2tp_recv_queue_skb(struct pppol2tp_session *session, struct sk_buff *skb){ struct sk_buff *skbp; u16 ns = PPPOL2TP_SKB_CB(skb)->ns; spin_lock(&session->reorder_q.lock); skb_queue_walk(&session->reorder_q, skbp) { if (PPPOL2TP_SKB_CB(skbp)->ns > ns) { __skb_insert(skb, skbp->prev, skbp, &session->reorder_q); PRINTK(session->debug, PPPOL2TP_MSG_SEQ, KERN_DEBUG, "%s: pkt %hu, inserted before %hu, reorder_q len=%d\n", session->name, ns, PPPOL2TP_SKB_CB(skbp)->ns, skb_queue_len(&session->reorder_q)); session->stats.rx_oos_packets++; goto out; } } __skb_queue_tail(&session->reorder_q, skb);out: spin_unlock(&session->reorder_q.lock);}/* Dequeue a single skb. */static void pppol2tp_recv_dequeue_skb(struct pppol2tp_session *session, struct sk_buff *skb){ struct pppol2tp_tunnel *tunnel = session->tunnel; int length = PPPOL2TP_SKB_CB(skb)->length; struct sock *session_sock = NULL; /* We're about to requeue the skb, so unlink it and return resources * to its current owner (a socket receive buffer). */ skb_unlink(skb, &session->reorder_q); skb_orphan(skb); tunnel->stats.rx_packets++; tunnel->stats.rx_bytes += length; session->stats.rx_packets++; session->stats.rx_bytes += length; if (PPPOL2TP_SKB_CB(skb)->has_seq) { /* Bump our Nr */ session->nr++; PRINTK(session->debug, PPPOL2TP_MSG_SEQ, KERN_DEBUG, "%s: updated nr to %hu\n", session->name, session->nr); } /* If the socket is bound, send it in to PPP's input queue. Otherwise * queue it on the session socket. */ session_sock = session->sock; if (session_sock->sk_state & PPPOX_BOUND) { struct pppox_sock *po; PRINTK(session->debug, PPPOL2TP_MSG_DATA, KERN_DEBUG, "%s: recv %d byte data frame, passing to ppp\n", session->name, length); /* We need to forget all info related to the L2TP packet * gathered in the skb as we are going to reuse the same * skb for the inner packet. * Namely we need to: * - reset xfrm (IPSec) information as it applies to * the outer L2TP packet and not to the inner one * - release the dst to force a route lookup on the inner * IP packet since skb->dst currently points to the dst * of the UDP tunnel * - reset netfilter information as it doesn't apply * to the inner packet either */ secpath_reset(skb); dst_release(skb->dst); skb->dst = NULL; nf_reset(skb); po = pppox_sk(session_sock); ppp_input(&po->chan, skb); } else { PRINTK(session->debug, PPPOL2TP_MSG_DATA, KERN_INFO, "%s: socket not bound\n", session->name); /* Not bound. Nothing we can do, so discard. */ session->stats.rx_errors++; kfree_skb(skb); } sock_put(session->sock);}/* 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 pppol2tp_recv_dequeue(struct pppol2tp_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. */ spin_lock(&session->reorder_q.lock); skb_queue_walk_safe(&session->reorder_q, skb, tmp) { if (time_after(jiffies, PPPOL2TP_SKB_CB(skb)->expires)) { session->stats.rx_seq_discards++; session->stats.rx_errors++; PRINTK(session->debug, PPPOL2TP_MSG_SEQ, KERN_DEBUG, "%s: oos pkt %hu len %d discarded (too old), " "waiting for %hu, reorder_q_len=%d\n", session->name, PPPOL2TP_SKB_CB(skb)->ns, PPPOL2TP_SKB_CB(skb)->length, session->nr, skb_queue_len(&session->reorder_q)); __skb_unlink(skb, &session->reorder_q); kfree_skb(skb); continue; } if (PPPOL2TP_SKB_CB(skb)->has_seq) { if (PPPOL2TP_SKB_CB(skb)->ns != session->nr) { PRINTK(session->debug, PPPOL2TP_MSG_SEQ, KERN_DEBUG, "%s: holding oos pkt %hu len %d, " "waiting for %hu, reorder_q_len=%d\n", session->name, PPPOL2TP_SKB_CB(skb)->ns, PPPOL2TP_SKB_CB(skb)->length, session->nr, skb_queue_len(&session->reorder_q)); goto out; } } spin_unlock(&session->reorder_q.lock); pppol2tp_recv_dequeue_skb(session, skb); spin_lock(&session->reorder_q.lock); }out: spin_unlock(&session->reorder_q.lock);}/* Internal receive frame. Do the real work of receiving an L2TP data frame * here. The skb is not on a list when we get here. * Returns 0 if the packet was a data packet and was successfully passed on. * Returns 1 if the packet was not a good data packet and could not be * forwarded. All such packets are passed up to userspace to deal with. */static int pppol2tp_recv_core(struct sock *sock, struct sk_buff *skb){ struct pppol2tp_session *session = NULL; struct pppol2tp_tunnel *tunnel; unsigned char *ptr, *optr; u16 hdrflags; u16 tunnel_id, session_id; int length; int offset;
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?