Initial commit
This commit is contained in:
commit
169c65d57e
51358 changed files with 23120455 additions and 0 deletions
108
net/l2tp/Kconfig
Normal file
108
net/l2tp/Kconfig
Normal file
|
@ -0,0 +1,108 @@
|
|||
#
|
||||
# Layer Two Tunneling Protocol (L2TP)
|
||||
#
|
||||
|
||||
menuconfig L2TP
|
||||
tristate "Layer Two Tunneling Protocol (L2TP)"
|
||||
depends on (IPV6 || IPV6=n)
|
||||
depends on INET
|
||||
---help---
|
||||
Layer Two Tunneling Protocol
|
||||
|
||||
From RFC 2661 <http://www.ietf.org/rfc/rfc2661.txt>.
|
||||
|
||||
L2TP facilitates the tunneling of packets across an
|
||||
intervening network in a way that is as transparent as
|
||||
possible to both end-users and applications.
|
||||
|
||||
L2TP is often used to tunnel PPP traffic over IP
|
||||
tunnels. One IP tunnel may carry thousands of individual PPP
|
||||
connections. L2TP is also used as a VPN protocol, popular
|
||||
with home workers to connect to their offices.
|
||||
|
||||
L2TPv3 allows other protocols as well as PPP to be carried
|
||||
over L2TP tunnels. L2TPv3 is defined in RFC 3931
|
||||
<http://www.ietf.org/rfc/rfc3931.txt>.
|
||||
|
||||
The kernel component handles only L2TP data packets: a
|
||||
userland daemon handles L2TP the control protocol (tunnel
|
||||
and session setup). One such daemon is OpenL2TP
|
||||
(http://openl2tp.org/).
|
||||
|
||||
If you don't need L2TP, say N. To compile all L2TP code as
|
||||
modules, choose M here.
|
||||
|
||||
config L2TP_DEBUGFS
|
||||
tristate "L2TP debugfs support"
|
||||
depends on L2TP && DEBUG_FS
|
||||
help
|
||||
Support for l2tp directory in debugfs filesystem. This may be
|
||||
used to dump internal state of the l2tp drivers for problem
|
||||
analysis.
|
||||
|
||||
If unsure, say 'Y'.
|
||||
|
||||
To compile this driver as a module, choose M here. The module
|
||||
will be called l2tp_debugfs.
|
||||
|
||||
config L2TP_V3
|
||||
bool "L2TPv3 support"
|
||||
depends on L2TP
|
||||
help
|
||||
Layer Two Tunneling Protocol Version 3
|
||||
|
||||
From RFC 3931 <http://www.ietf.org/rfc/rfc3931.txt>.
|
||||
|
||||
The Layer Two Tunneling Protocol (L2TP) provides a dynamic
|
||||
mechanism for tunneling Layer 2 (L2) "circuits" across a
|
||||
packet-oriented data network (e.g., over IP). L2TP, as
|
||||
originally defined in RFC 2661, is a standard method for
|
||||
tunneling Point-to-Point Protocol (PPP) [RFC1661] sessions.
|
||||
L2TP has since been adopted for tunneling a number of other
|
||||
L2 protocols, including ATM, Frame Relay, HDLC and even raw
|
||||
ethernet frames.
|
||||
|
||||
If you are connecting to L2TPv3 equipment, or you want to
|
||||
tunnel raw ethernet frames using L2TP, say Y here. If
|
||||
unsure, say N.
|
||||
|
||||
config L2TP_IP
|
||||
tristate "L2TP IP encapsulation for L2TPv3"
|
||||
depends on L2TP_V3
|
||||
help
|
||||
Support for L2TP-over-IP socket family.
|
||||
|
||||
The L2TPv3 protocol defines two possible encapsulations for
|
||||
L2TP frames, namely UDP and plain IP (without UDP). This
|
||||
driver provides a new L2TPIP socket family with which
|
||||
userspace L2TPv3 daemons may create L2TP/IP tunnel sockets
|
||||
when UDP encapsulation is not required. When L2TP is carried
|
||||
in IP packets, it used IP protocol number 115, so this port
|
||||
must be enabled in firewalls.
|
||||
|
||||
To compile this driver as a module, choose M here. The module
|
||||
will be called l2tp_ip.
|
||||
|
||||
config L2TP_ETH
|
||||
tristate "L2TP ethernet pseudowire support for L2TPv3"
|
||||
depends on L2TP_V3
|
||||
help
|
||||
Support for carrying raw ethernet frames over L2TPv3.
|
||||
|
||||
From RFC 4719 <http://www.ietf.org/rfc/rfc4719.txt>.
|
||||
|
||||
The Layer 2 Tunneling Protocol, Version 3 (L2TPv3) can be
|
||||
used as a control protocol and for data encapsulation to set
|
||||
up Pseudowires for transporting layer 2 Packet Data Units
|
||||
across an IP network [RFC3931].
|
||||
|
||||
This driver provides an ethernet virtual interface for each
|
||||
L2TP ethernet pseudowire instance. Standard Linux tools may
|
||||
be used to assign an IP address to the local virtual
|
||||
interface, or add the interface to a bridge.
|
||||
|
||||
If you are using L2TPv3, you will almost certainly want to
|
||||
enable this option.
|
||||
|
||||
To compile this driver as a module, choose M here. The module
|
||||
will be called l2tp_eth.
|
15
net/l2tp/Makefile
Normal file
15
net/l2tp/Makefile
Normal file
|
@ -0,0 +1,15 @@
|
|||
#
|
||||
# Makefile for the L2TP.
|
||||
#
|
||||
|
||||
obj-$(CONFIG_L2TP) += l2tp_core.o
|
||||
|
||||
# Build l2tp as modules if L2TP is M
|
||||
obj-$(subst y,$(CONFIG_L2TP),$(CONFIG_PPPOL2TP)) += l2tp_ppp.o
|
||||
obj-$(subst y,$(CONFIG_L2TP),$(CONFIG_L2TP_IP)) += l2tp_ip.o
|
||||
obj-$(subst y,$(CONFIG_L2TP),$(CONFIG_L2TP_V3)) += l2tp_netlink.o
|
||||
obj-$(subst y,$(CONFIG_L2TP),$(CONFIG_L2TP_ETH)) += l2tp_eth.o
|
||||
obj-$(subst y,$(CONFIG_L2TP),$(CONFIG_L2TP_DEBUGFS)) += l2tp_debugfs.o
|
||||
ifneq ($(CONFIG_IPV6),)
|
||||
obj-$(subst y,$(CONFIG_L2TP),$(CONFIG_L2TP_IP)) += l2tp_ip6.o
|
||||
endif
|
1984
net/l2tp/l2tp_core.c
Normal file
1984
net/l2tp/l2tp_core.c
Normal file
File diff suppressed because it is too large
Load diff
307
net/l2tp/l2tp_core.h
Normal file
307
net/l2tp/l2tp_core.h
Normal file
|
@ -0,0 +1,307 @@
|
|||
/*
|
||||
* L2TP internal definitions.
|
||||
*
|
||||
* Copyright (c) 2008,2009 Katalix Systems Ltd
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef _L2TP_CORE_H_
|
||||
#define _L2TP_CORE_H_
|
||||
|
||||
/* Just some random numbers */
|
||||
#define L2TP_TUNNEL_MAGIC 0x42114DDA
|
||||
#define L2TP_SESSION_MAGIC 0x0C04EB7D
|
||||
|
||||
/* Per tunnel, session hash table size */
|
||||
#define L2TP_HASH_BITS 4
|
||||
#define L2TP_HASH_SIZE (1 << L2TP_HASH_BITS)
|
||||
|
||||
/* System-wide, session hash table size */
|
||||
#define L2TP_HASH_BITS_2 8
|
||||
#define L2TP_HASH_SIZE_2 (1 << L2TP_HASH_BITS_2)
|
||||
|
||||
/* Debug message categories for the DEBUG socket option */
|
||||
enum {
|
||||
L2TP_MSG_DEBUG = (1 << 0), /* verbose debug (if
|
||||
* compiled in) */
|
||||
L2TP_MSG_CONTROL = (1 << 1), /* userspace - kernel
|
||||
* interface */
|
||||
L2TP_MSG_SEQ = (1 << 2), /* sequence numbers */
|
||||
L2TP_MSG_DATA = (1 << 3), /* data packets */
|
||||
};
|
||||
|
||||
struct sk_buff;
|
||||
|
||||
struct l2tp_stats {
|
||||
atomic_long_t tx_packets;
|
||||
atomic_long_t tx_bytes;
|
||||
atomic_long_t tx_errors;
|
||||
atomic_long_t rx_packets;
|
||||
atomic_long_t rx_bytes;
|
||||
atomic_long_t rx_seq_discards;
|
||||
atomic_long_t rx_oos_packets;
|
||||
atomic_long_t rx_errors;
|
||||
atomic_long_t rx_cookie_discards;
|
||||
};
|
||||
|
||||
struct l2tp_tunnel;
|
||||
|
||||
/* Describes a session. Contains information to determine incoming
|
||||
* packets and transmit outgoing ones.
|
||||
*/
|
||||
struct l2tp_session_cfg {
|
||||
enum l2tp_pwtype pw_type;
|
||||
unsigned int data_seq:2; /* data sequencing level
|
||||
* 0 => none, 1 => IP only,
|
||||
* 2 => all
|
||||
*/
|
||||
unsigned int recv_seq:1; /* expect receive packets with
|
||||
* sequence numbers? */
|
||||
unsigned int send_seq:1; /* send packets with sequence
|
||||
* numbers? */
|
||||
unsigned int lns_mode:1; /* behave as LNS? LAC enables
|
||||
* sequence numbers under
|
||||
* control of LNS. */
|
||||
int debug; /* bitmask of debug message
|
||||
* categories */
|
||||
u16 vlan_id; /* VLAN pseudowire only */
|
||||
u16 offset; /* offset to payload */
|
||||
u16 l2specific_len; /* Layer 2 specific length */
|
||||
u16 l2specific_type; /* Layer 2 specific type */
|
||||
u8 cookie[8]; /* optional cookie */
|
||||
int cookie_len; /* 0, 4 or 8 bytes */
|
||||
u8 peer_cookie[8]; /* peer's cookie */
|
||||
int peer_cookie_len; /* 0, 4 or 8 bytes */
|
||||
int reorder_timeout; /* configured reorder timeout
|
||||
* (in jiffies) */
|
||||
int mtu;
|
||||
int mru;
|
||||
char *ifname;
|
||||
};
|
||||
|
||||
struct l2tp_session {
|
||||
int magic; /* should be
|
||||
* L2TP_SESSION_MAGIC */
|
||||
|
||||
struct l2tp_tunnel *tunnel; /* back pointer to tunnel
|
||||
* context */
|
||||
u32 session_id;
|
||||
u32 peer_session_id;
|
||||
u8 cookie[8];
|
||||
int cookie_len;
|
||||
u8 peer_cookie[8];
|
||||
int peer_cookie_len;
|
||||
u16 offset; /* offset from end of L2TP header
|
||||
to beginning of data */
|
||||
u16 l2specific_len;
|
||||
u16 l2specific_type;
|
||||
u16 hdr_len;
|
||||
u32 nr; /* session NR state (receive) */
|
||||
u32 ns; /* session NR state (send) */
|
||||
struct sk_buff_head reorder_q; /* receive reorder queue */
|
||||
struct hlist_node hlist; /* Hash list node */
|
||||
atomic_t ref_count;
|
||||
|
||||
char name[32]; /* for logging */
|
||||
char ifname[IFNAMSIZ];
|
||||
unsigned int data_seq:2; /* data sequencing level
|
||||
* 0 => none, 1 => IP only,
|
||||
* 2 => all
|
||||
*/
|
||||
unsigned int recv_seq:1; /* expect receive packets with
|
||||
* sequence numbers? */
|
||||
unsigned int send_seq:1; /* send packets with sequence
|
||||
* numbers? */
|
||||
unsigned int 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) */
|
||||
int reorder_skip; /* set if skip to next nr */
|
||||
int mtu;
|
||||
int mru;
|
||||
enum l2tp_pwtype pwtype;
|
||||
struct l2tp_stats stats;
|
||||
struct hlist_node global_hlist; /* Global hash list node */
|
||||
|
||||
int (*build_header)(struct l2tp_session *session, void *buf);
|
||||
void (*recv_skb)(struct l2tp_session *session, struct sk_buff *skb, int data_len);
|
||||
void (*session_close)(struct l2tp_session *session);
|
||||
void (*ref)(struct l2tp_session *session);
|
||||
void (*deref)(struct l2tp_session *session);
|
||||
#if defined(CONFIG_L2TP_DEBUGFS) || defined(CONFIG_L2TP_DEBUGFS_MODULE)
|
||||
void (*show)(struct seq_file *m, void *priv);
|
||||
#endif
|
||||
uint8_t priv[0]; /* private data */
|
||||
};
|
||||
|
||||
/* Describes the tunnel. It contains info to track all the associated
|
||||
* sessions so incoming packets can be sorted out
|
||||
*/
|
||||
struct l2tp_tunnel_cfg {
|
||||
int debug; /* bitmask of debug message
|
||||
* categories */
|
||||
enum l2tp_encap_type encap;
|
||||
|
||||
/* Used only for kernel-created sockets */
|
||||
struct in_addr local_ip;
|
||||
struct in_addr peer_ip;
|
||||
#if IS_ENABLED(CONFIG_IPV6)
|
||||
struct in6_addr *local_ip6;
|
||||
struct in6_addr *peer_ip6;
|
||||
#endif
|
||||
u16 local_udp_port;
|
||||
u16 peer_udp_port;
|
||||
unsigned int use_udp_checksums:1;
|
||||
};
|
||||
|
||||
struct l2tp_tunnel {
|
||||
int magic; /* Should be L2TP_TUNNEL_MAGIC */
|
||||
struct rcu_head rcu;
|
||||
rwlock_t hlist_lock; /* protect session_hlist */
|
||||
struct hlist_head session_hlist[L2TP_HASH_SIZE];
|
||||
/* hashed list of sessions,
|
||||
* hashed by id */
|
||||
u32 tunnel_id;
|
||||
u32 peer_tunnel_id;
|
||||
int version; /* 2=>L2TPv2, 3=>L2TPv3 */
|
||||
|
||||
char name[20]; /* for logging */
|
||||
int debug; /* bitmask of debug message
|
||||
* categories */
|
||||
enum l2tp_encap_type encap;
|
||||
struct l2tp_stats stats;
|
||||
|
||||
struct list_head list; /* Keep a list of all tunnels */
|
||||
struct net *l2tp_net; /* the net we belong to */
|
||||
|
||||
atomic_t ref_count;
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
void (*show)(struct seq_file *m, void *arg);
|
||||
#endif
|
||||
int (*recv_payload_hook)(struct sk_buff *skb);
|
||||
void (*old_sk_destruct)(struct sock *);
|
||||
struct sock *sock; /* Parent socket */
|
||||
int fd; /* Parent fd, if tunnel socket
|
||||
* was created by userspace */
|
||||
#if IS_ENABLED(CONFIG_IPV6)
|
||||
bool v4mapped;
|
||||
#endif
|
||||
|
||||
struct work_struct del_work;
|
||||
|
||||
uint8_t priv[0]; /* private data */
|
||||
};
|
||||
|
||||
struct l2tp_nl_cmd_ops {
|
||||
int (*session_create)(struct net *net, u32 tunnel_id, u32 session_id, u32 peer_session_id, struct l2tp_session_cfg *cfg);
|
||||
int (*session_delete)(struct l2tp_session *session);
|
||||
};
|
||||
|
||||
static inline void *l2tp_tunnel_priv(struct l2tp_tunnel *tunnel)
|
||||
{
|
||||
return &tunnel->priv[0];
|
||||
}
|
||||
|
||||
static inline void *l2tp_session_priv(struct l2tp_session *session)
|
||||
{
|
||||
return &session->priv[0];
|
||||
}
|
||||
|
||||
static inline struct l2tp_tunnel *l2tp_sock_to_tunnel(struct sock *sk)
|
||||
{
|
||||
struct l2tp_tunnel *tunnel;
|
||||
|
||||
if (sk == NULL)
|
||||
return NULL;
|
||||
|
||||
sock_hold(sk);
|
||||
tunnel = (struct l2tp_tunnel *)(sk->sk_user_data);
|
||||
if (tunnel == NULL) {
|
||||
sock_put(sk);
|
||||
goto out;
|
||||
}
|
||||
|
||||
BUG_ON(tunnel->magic != L2TP_TUNNEL_MAGIC);
|
||||
|
||||
out:
|
||||
return tunnel;
|
||||
}
|
||||
|
||||
extern struct sock *l2tp_tunnel_sock_lookup(struct l2tp_tunnel *tunnel);
|
||||
extern void l2tp_tunnel_sock_put(struct sock *sk);
|
||||
extern struct l2tp_session *l2tp_session_find(struct net *net, struct l2tp_tunnel *tunnel, u32 session_id);
|
||||
extern struct l2tp_session *l2tp_session_find_nth(struct l2tp_tunnel *tunnel, int nth);
|
||||
extern struct l2tp_session *l2tp_session_find_by_ifname(struct net *net, char *ifname);
|
||||
extern struct l2tp_tunnel *l2tp_tunnel_find(struct net *net, u32 tunnel_id);
|
||||
extern struct l2tp_tunnel *l2tp_tunnel_find_nth(struct net *net, int nth);
|
||||
|
||||
extern int l2tp_tunnel_create(struct net *net, int fd, int version, u32 tunnel_id, u32 peer_tunnel_id, struct l2tp_tunnel_cfg *cfg, struct l2tp_tunnel **tunnelp);
|
||||
extern void l2tp_tunnel_closeall(struct l2tp_tunnel *tunnel);
|
||||
extern int l2tp_tunnel_delete(struct l2tp_tunnel *tunnel);
|
||||
extern struct l2tp_session *l2tp_session_create(int priv_size, struct l2tp_tunnel *tunnel, u32 session_id, u32 peer_session_id, struct l2tp_session_cfg *cfg);
|
||||
extern void __l2tp_session_unhash(struct l2tp_session *session);
|
||||
extern int l2tp_session_delete(struct l2tp_session *session);
|
||||
extern void l2tp_session_free(struct l2tp_session *session);
|
||||
extern 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));
|
||||
extern int l2tp_session_queue_purge(struct l2tp_session *session);
|
||||
extern int l2tp_udp_encap_recv(struct sock *sk, struct sk_buff *skb);
|
||||
|
||||
extern int l2tp_xmit_skb(struct l2tp_session *session, struct sk_buff *skb, int hdr_len);
|
||||
|
||||
extern int l2tp_nl_register_ops(enum l2tp_pwtype pw_type, const struct l2tp_nl_cmd_ops *ops);
|
||||
extern void l2tp_nl_unregister_ops(enum l2tp_pwtype pw_type);
|
||||
|
||||
/* Session reference counts. Incremented when code obtains a reference
|
||||
* to a session.
|
||||
*/
|
||||
static inline void l2tp_session_inc_refcount_1(struct l2tp_session *session)
|
||||
{
|
||||
atomic_inc(&session->ref_count);
|
||||
}
|
||||
|
||||
static inline void l2tp_session_dec_refcount_1(struct l2tp_session *session)
|
||||
{
|
||||
if (atomic_dec_and_test(&session->ref_count))
|
||||
l2tp_session_free(session);
|
||||
}
|
||||
|
||||
#ifdef L2TP_REFCNT_DEBUG
|
||||
#define l2tp_session_inc_refcount(_s) \
|
||||
do { \
|
||||
pr_debug("l2tp_session_inc_refcount: %s:%d %s: cnt=%d\n", \
|
||||
__func__, __LINE__, (_s)->name, \
|
||||
atomic_read(&_s->ref_count)); \
|
||||
l2tp_session_inc_refcount_1(_s); \
|
||||
} while (0)
|
||||
#define l2tp_session_dec_refcount(_s) \
|
||||
do { \
|
||||
pr_debug("l2tp_session_dec_refcount: %s:%d %s: cnt=%d\n", \
|
||||
__func__, __LINE__, (_s)->name, \
|
||||
atomic_read(&_s->ref_count)); \
|
||||
l2tp_session_dec_refcount_1(_s); \
|
||||
} while (0)
|
||||
#else
|
||||
#define l2tp_session_inc_refcount(s) l2tp_session_inc_refcount_1(s)
|
||||
#define l2tp_session_dec_refcount(s) l2tp_session_dec_refcount_1(s)
|
||||
#endif
|
||||
|
||||
#define l2tp_printk(ptr, type, func, fmt, ...) \
|
||||
do { \
|
||||
if (((ptr)->debug) & (type)) \
|
||||
func(fmt, ##__VA_ARGS__); \
|
||||
} while (0)
|
||||
|
||||
#define l2tp_warn(ptr, type, fmt, ...) \
|
||||
l2tp_printk(ptr, type, pr_warn, fmt, ##__VA_ARGS__)
|
||||
#define l2tp_info(ptr, type, fmt, ...) \
|
||||
l2tp_printk(ptr, type, pr_info, fmt, ##__VA_ARGS__)
|
||||
#define l2tp_dbg(ptr, type, fmt, ...) \
|
||||
l2tp_printk(ptr, type, pr_debug, fmt, ##__VA_ARGS__)
|
||||
|
||||
#endif /* _L2TP_CORE_H_ */
|
351
net/l2tp/l2tp_debugfs.c
Normal file
351
net/l2tp/l2tp_debugfs.c
Normal file
|
@ -0,0 +1,351 @@
|
|||
/*
|
||||
* L2TP subsystem debugfs
|
||||
*
|
||||
* Copyright (c) 2010 Katalix Systems Ltd
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/skbuff.h>
|
||||
#include <linux/socket.h>
|
||||
#include <linux/hash.h>
|
||||
#include <linux/l2tp.h>
|
||||
#include <linux/in.h>
|
||||
#include <linux/etherdevice.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <net/sock.h>
|
||||
#include <net/ip.h>
|
||||
#include <net/icmp.h>
|
||||
#include <net/udp.h>
|
||||
#include <net/inet_common.h>
|
||||
#include <net/inet_hashtables.h>
|
||||
#include <net/tcp_states.h>
|
||||
#include <net/protocol.h>
|
||||
#include <net/xfrm.h>
|
||||
#include <net/net_namespace.h>
|
||||
#include <net/netns/generic.h>
|
||||
|
||||
#include "l2tp_core.h"
|
||||
|
||||
static struct dentry *rootdir;
|
||||
static struct dentry *tunnels;
|
||||
|
||||
struct l2tp_dfs_seq_data {
|
||||
struct net *net;
|
||||
int tunnel_idx; /* current tunnel */
|
||||
int session_idx; /* index of session within current tunnel */
|
||||
struct l2tp_tunnel *tunnel;
|
||||
struct l2tp_session *session; /* NULL means get next tunnel */
|
||||
};
|
||||
|
||||
static void l2tp_dfs_next_tunnel(struct l2tp_dfs_seq_data *pd)
|
||||
{
|
||||
pd->tunnel = l2tp_tunnel_find_nth(pd->net, pd->tunnel_idx);
|
||||
pd->tunnel_idx++;
|
||||
}
|
||||
|
||||
static void l2tp_dfs_next_session(struct l2tp_dfs_seq_data *pd)
|
||||
{
|
||||
pd->session = l2tp_session_find_nth(pd->tunnel, pd->session_idx);
|
||||
pd->session_idx++;
|
||||
|
||||
if (pd->session == NULL) {
|
||||
pd->session_idx = 0;
|
||||
l2tp_dfs_next_tunnel(pd);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void *l2tp_dfs_seq_start(struct seq_file *m, loff_t *offs)
|
||||
{
|
||||
struct l2tp_dfs_seq_data *pd = SEQ_START_TOKEN;
|
||||
loff_t pos = *offs;
|
||||
|
||||
if (!pos)
|
||||
goto out;
|
||||
|
||||
BUG_ON(m->private == NULL);
|
||||
pd = m->private;
|
||||
|
||||
if (pd->tunnel == NULL)
|
||||
l2tp_dfs_next_tunnel(pd);
|
||||
else
|
||||
l2tp_dfs_next_session(pd);
|
||||
|
||||
/* NULL tunnel and session indicates end of list */
|
||||
if ((pd->tunnel == NULL) && (pd->session == NULL))
|
||||
pd = NULL;
|
||||
|
||||
out:
|
||||
return pd;
|
||||
}
|
||||
|
||||
|
||||
static void *l2tp_dfs_seq_next(struct seq_file *m, void *v, loff_t *pos)
|
||||
{
|
||||
(*pos)++;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void l2tp_dfs_seq_stop(struct seq_file *p, void *v)
|
||||
{
|
||||
/* nothing to do */
|
||||
}
|
||||
|
||||
static void l2tp_dfs_seq_tunnel_show(struct seq_file *m, void *v)
|
||||
{
|
||||
struct l2tp_tunnel *tunnel = v;
|
||||
int session_count = 0;
|
||||
int hash;
|
||||
struct hlist_node *walk;
|
||||
struct hlist_node *tmp;
|
||||
|
||||
read_lock_bh(&tunnel->hlist_lock);
|
||||
for (hash = 0; hash < L2TP_HASH_SIZE; hash++) {
|
||||
hlist_for_each_safe(walk, tmp, &tunnel->session_hlist[hash]) {
|
||||
struct l2tp_session *session;
|
||||
|
||||
session = hlist_entry(walk, struct l2tp_session, hlist);
|
||||
if (session->session_id == 0)
|
||||
continue;
|
||||
|
||||
session_count++;
|
||||
}
|
||||
}
|
||||
read_unlock_bh(&tunnel->hlist_lock);
|
||||
|
||||
seq_printf(m, "\nTUNNEL %u peer %u", tunnel->tunnel_id, tunnel->peer_tunnel_id);
|
||||
if (tunnel->sock) {
|
||||
struct inet_sock *inet = inet_sk(tunnel->sock);
|
||||
|
||||
#if IS_ENABLED(CONFIG_IPV6)
|
||||
if (tunnel->sock->sk_family == AF_INET6) {
|
||||
struct ipv6_pinfo *np = inet6_sk(tunnel->sock);
|
||||
seq_printf(m, " from %pI6c to %pI6c\n",
|
||||
&np->saddr, &np->daddr);
|
||||
} else
|
||||
#endif
|
||||
seq_printf(m, " from %pI4 to %pI4\n",
|
||||
&inet->inet_saddr, &inet->inet_daddr);
|
||||
if (tunnel->encap == L2TP_ENCAPTYPE_UDP)
|
||||
seq_printf(m, " source port %hu, dest port %hu\n",
|
||||
ntohs(inet->inet_sport), ntohs(inet->inet_dport));
|
||||
}
|
||||
seq_printf(m, " L2TPv%d, %s\n", tunnel->version,
|
||||
tunnel->encap == L2TP_ENCAPTYPE_UDP ? "UDP" :
|
||||
tunnel->encap == L2TP_ENCAPTYPE_IP ? "IP" :
|
||||
"");
|
||||
seq_printf(m, " %d sessions, refcnt %d/%d\n", session_count,
|
||||
tunnel->sock ? atomic_read(&tunnel->sock->sk_refcnt) : 0,
|
||||
atomic_read(&tunnel->ref_count));
|
||||
|
||||
seq_printf(m, " %08x rx %ld/%ld/%ld rx %ld/%ld/%ld\n",
|
||||
tunnel->debug,
|
||||
atomic_long_read(&tunnel->stats.tx_packets),
|
||||
atomic_long_read(&tunnel->stats.tx_bytes),
|
||||
atomic_long_read(&tunnel->stats.tx_errors),
|
||||
atomic_long_read(&tunnel->stats.rx_packets),
|
||||
atomic_long_read(&tunnel->stats.rx_bytes),
|
||||
atomic_long_read(&tunnel->stats.rx_errors));
|
||||
|
||||
if (tunnel->show != NULL)
|
||||
tunnel->show(m, tunnel);
|
||||
}
|
||||
|
||||
static void l2tp_dfs_seq_session_show(struct seq_file *m, void *v)
|
||||
{
|
||||
struct l2tp_session *session = v;
|
||||
|
||||
seq_printf(m, " SESSION %u, peer %u, %s\n", session->session_id,
|
||||
session->peer_session_id,
|
||||
session->pwtype == L2TP_PWTYPE_ETH ? "ETH" :
|
||||
session->pwtype == L2TP_PWTYPE_PPP ? "PPP" :
|
||||
"");
|
||||
if (session->send_seq || session->recv_seq)
|
||||
seq_printf(m, " nr %hu, ns %hu\n", session->nr, session->ns);
|
||||
seq_printf(m, " refcnt %d\n", atomic_read(&session->ref_count));
|
||||
seq_printf(m, " config %d/%d/%c/%c/%s/%s %08x %u\n",
|
||||
session->mtu, session->mru,
|
||||
session->recv_seq ? 'R' : '-',
|
||||
session->send_seq ? 'S' : '-',
|
||||
session->data_seq == 1 ? "IPSEQ" :
|
||||
session->data_seq == 2 ? "DATASEQ" : "-",
|
||||
session->lns_mode ? "LNS" : "LAC",
|
||||
session->debug,
|
||||
jiffies_to_msecs(session->reorder_timeout));
|
||||
seq_printf(m, " offset %hu l2specific %hu/%hu\n",
|
||||
session->offset, session->l2specific_type, session->l2specific_len);
|
||||
if (session->cookie_len) {
|
||||
seq_printf(m, " cookie %02x%02x%02x%02x",
|
||||
session->cookie[0], session->cookie[1],
|
||||
session->cookie[2], session->cookie[3]);
|
||||
if (session->cookie_len == 8)
|
||||
seq_printf(m, "%02x%02x%02x%02x",
|
||||
session->cookie[4], session->cookie[5],
|
||||
session->cookie[6], session->cookie[7]);
|
||||
seq_printf(m, "\n");
|
||||
}
|
||||
if (session->peer_cookie_len) {
|
||||
seq_printf(m, " peer cookie %02x%02x%02x%02x",
|
||||
session->peer_cookie[0], session->peer_cookie[1],
|
||||
session->peer_cookie[2], session->peer_cookie[3]);
|
||||
if (session->peer_cookie_len == 8)
|
||||
seq_printf(m, "%02x%02x%02x%02x",
|
||||
session->peer_cookie[4], session->peer_cookie[5],
|
||||
session->peer_cookie[6], session->peer_cookie[7]);
|
||||
seq_printf(m, "\n");
|
||||
}
|
||||
|
||||
seq_printf(m, " %hu/%hu tx %ld/%ld/%ld rx %ld/%ld/%ld\n",
|
||||
session->nr, session->ns,
|
||||
atomic_long_read(&session->stats.tx_packets),
|
||||
atomic_long_read(&session->stats.tx_bytes),
|
||||
atomic_long_read(&session->stats.tx_errors),
|
||||
atomic_long_read(&session->stats.rx_packets),
|
||||
atomic_long_read(&session->stats.rx_bytes),
|
||||
atomic_long_read(&session->stats.rx_errors));
|
||||
|
||||
if (session->show != NULL)
|
||||
session->show(m, session);
|
||||
}
|
||||
|
||||
static int l2tp_dfs_seq_show(struct seq_file *m, void *v)
|
||||
{
|
||||
struct l2tp_dfs_seq_data *pd = v;
|
||||
|
||||
/* display header on line 1 */
|
||||
if (v == SEQ_START_TOKEN) {
|
||||
seq_puts(m, "TUNNEL ID, peer ID from IP to IP\n");
|
||||
seq_puts(m, " L2TPv2/L2TPv3, UDP/IP\n");
|
||||
seq_puts(m, " sessions session-count, refcnt refcnt/sk->refcnt\n");
|
||||
seq_puts(m, " debug tx-pkts/bytes/errs rx-pkts/bytes/errs\n");
|
||||
seq_puts(m, " SESSION ID, peer ID, PWTYPE\n");
|
||||
seq_puts(m, " refcnt cnt\n");
|
||||
seq_puts(m, " offset OFFSET l2specific TYPE/LEN\n");
|
||||
seq_puts(m, " [ cookie ]\n");
|
||||
seq_puts(m, " [ peer cookie ]\n");
|
||||
seq_puts(m, " config mtu/mru/rcvseq/sendseq/dataseq/lns debug reorderto\n");
|
||||
seq_puts(m, " nr/ns tx-pkts/bytes/errs rx-pkts/bytes/errs\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Show the tunnel or session context */
|
||||
if (pd->session == NULL)
|
||||
l2tp_dfs_seq_tunnel_show(m, pd->tunnel);
|
||||
else
|
||||
l2tp_dfs_seq_session_show(m, pd->session);
|
||||
|
||||
out:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct seq_operations l2tp_dfs_seq_ops = {
|
||||
.start = l2tp_dfs_seq_start,
|
||||
.next = l2tp_dfs_seq_next,
|
||||
.stop = l2tp_dfs_seq_stop,
|
||||
.show = l2tp_dfs_seq_show,
|
||||
};
|
||||
|
||||
static int l2tp_dfs_seq_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct l2tp_dfs_seq_data *pd;
|
||||
struct seq_file *seq;
|
||||
int rc = -ENOMEM;
|
||||
|
||||
pd = kzalloc(sizeof(*pd), GFP_KERNEL);
|
||||
if (pd == NULL)
|
||||
goto out;
|
||||
|
||||
/* Derive the network namespace from the pid opening the
|
||||
* file.
|
||||
*/
|
||||
pd->net = get_net_ns_by_pid(current->pid);
|
||||
if (IS_ERR(pd->net)) {
|
||||
rc = PTR_ERR(pd->net);
|
||||
goto err_free_pd;
|
||||
}
|
||||
|
||||
rc = seq_open(file, &l2tp_dfs_seq_ops);
|
||||
if (rc)
|
||||
goto err_free_net;
|
||||
|
||||
seq = file->private_data;
|
||||
seq->private = pd;
|
||||
|
||||
out:
|
||||
return rc;
|
||||
|
||||
err_free_net:
|
||||
put_net(pd->net);
|
||||
err_free_pd:
|
||||
kfree(pd);
|
||||
goto out;
|
||||
}
|
||||
|
||||
static int l2tp_dfs_seq_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct l2tp_dfs_seq_data *pd;
|
||||
struct seq_file *seq;
|
||||
|
||||
seq = file->private_data;
|
||||
pd = seq->private;
|
||||
if (pd->net)
|
||||
put_net(pd->net);
|
||||
kfree(pd);
|
||||
seq_release(inode, file);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct file_operations l2tp_dfs_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = l2tp_dfs_seq_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = l2tp_dfs_seq_release,
|
||||
};
|
||||
|
||||
static int __init l2tp_debugfs_init(void)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
rootdir = debugfs_create_dir("l2tp", NULL);
|
||||
if (IS_ERR(rootdir)) {
|
||||
rc = PTR_ERR(rootdir);
|
||||
rootdir = NULL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
tunnels = debugfs_create_file("tunnels", 0600, rootdir, NULL, &l2tp_dfs_fops);
|
||||
if (tunnels == NULL)
|
||||
rc = -EIO;
|
||||
|
||||
pr_info("L2TP debugfs support\n");
|
||||
|
||||
out:
|
||||
if (rc)
|
||||
pr_warn("unable to init\n");
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void __exit l2tp_debugfs_exit(void)
|
||||
{
|
||||
debugfs_remove(tunnels);
|
||||
debugfs_remove(rootdir);
|
||||
}
|
||||
|
||||
module_init(l2tp_debugfs_init);
|
||||
module_exit(l2tp_debugfs_exit);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("James Chapman <jchapman@katalix.com>");
|
||||
MODULE_DESCRIPTION("L2TP debugfs driver");
|
||||
MODULE_VERSION("1.0");
|
358
net/l2tp/l2tp_eth.c
Normal file
358
net/l2tp/l2tp_eth.c
Normal file
|
@ -0,0 +1,358 @@
|
|||
/*
|
||||
* L2TPv3 ethernet pseudowire driver
|
||||
*
|
||||
* Copyright (c) 2008,2009,2010 Katalix Systems Ltd
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/skbuff.h>
|
||||
#include <linux/socket.h>
|
||||
#include <linux/hash.h>
|
||||
#include <linux/l2tp.h>
|
||||
#include <linux/in.h>
|
||||
#include <linux/etherdevice.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <net/sock.h>
|
||||
#include <net/ip.h>
|
||||
#include <net/icmp.h>
|
||||
#include <net/udp.h>
|
||||
#include <net/inet_common.h>
|
||||
#include <net/inet_hashtables.h>
|
||||
#include <net/tcp_states.h>
|
||||
#include <net/protocol.h>
|
||||
#include <net/xfrm.h>
|
||||
#include <net/net_namespace.h>
|
||||
#include <net/netns/generic.h>
|
||||
|
||||
#include "l2tp_core.h"
|
||||
|
||||
/* Default device name. May be overridden by name specified by user */
|
||||
#define L2TP_ETH_DEV_NAME "l2tpeth%d"
|
||||
|
||||
/* via netdev_priv() */
|
||||
struct l2tp_eth {
|
||||
struct net_device *dev;
|
||||
struct sock *tunnel_sock;
|
||||
struct l2tp_session *session;
|
||||
struct list_head list;
|
||||
atomic_long_t tx_bytes;
|
||||
atomic_long_t tx_packets;
|
||||
atomic_long_t tx_dropped;
|
||||
atomic_long_t rx_bytes;
|
||||
atomic_long_t rx_packets;
|
||||
atomic_long_t rx_errors;
|
||||
};
|
||||
|
||||
/* via l2tp_session_priv() */
|
||||
struct l2tp_eth_sess {
|
||||
struct net_device *dev;
|
||||
};
|
||||
|
||||
/* per-net private data for this module */
|
||||
static unsigned int l2tp_eth_net_id;
|
||||
struct l2tp_eth_net {
|
||||
struct list_head l2tp_eth_dev_list;
|
||||
spinlock_t l2tp_eth_lock;
|
||||
};
|
||||
|
||||
static inline struct l2tp_eth_net *l2tp_eth_pernet(struct net *net)
|
||||
{
|
||||
return net_generic(net, l2tp_eth_net_id);
|
||||
}
|
||||
|
||||
static struct lock_class_key l2tp_eth_tx_busylock;
|
||||
static int l2tp_eth_dev_init(struct net_device *dev)
|
||||
{
|
||||
struct l2tp_eth *priv = netdev_priv(dev);
|
||||
|
||||
priv->dev = dev;
|
||||
eth_hw_addr_random(dev);
|
||||
memset(&dev->broadcast[0], 0xff, 6);
|
||||
dev->qdisc_tx_busylock = &l2tp_eth_tx_busylock;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void l2tp_eth_dev_uninit(struct net_device *dev)
|
||||
{
|
||||
struct l2tp_eth *priv = netdev_priv(dev);
|
||||
struct l2tp_eth_net *pn = l2tp_eth_pernet(dev_net(dev));
|
||||
|
||||
spin_lock(&pn->l2tp_eth_lock);
|
||||
list_del_init(&priv->list);
|
||||
spin_unlock(&pn->l2tp_eth_lock);
|
||||
dev_put(dev);
|
||||
}
|
||||
|
||||
static int l2tp_eth_dev_xmit(struct sk_buff *skb, struct net_device *dev)
|
||||
{
|
||||
struct l2tp_eth *priv = netdev_priv(dev);
|
||||
struct l2tp_session *session = priv->session;
|
||||
unsigned int len = skb->len;
|
||||
int ret = l2tp_xmit_skb(session, skb, session->hdr_len);
|
||||
|
||||
if (likely(ret == NET_XMIT_SUCCESS)) {
|
||||
atomic_long_add(len, &priv->tx_bytes);
|
||||
atomic_long_inc(&priv->tx_packets);
|
||||
} else {
|
||||
atomic_long_inc(&priv->tx_dropped);
|
||||
}
|
||||
return NETDEV_TX_OK;
|
||||
}
|
||||
|
||||
static struct rtnl_link_stats64 *l2tp_eth_get_stats64(struct net_device *dev,
|
||||
struct rtnl_link_stats64 *stats)
|
||||
{
|
||||
struct l2tp_eth *priv = netdev_priv(dev);
|
||||
|
||||
stats->tx_bytes = atomic_long_read(&priv->tx_bytes);
|
||||
stats->tx_packets = atomic_long_read(&priv->tx_packets);
|
||||
stats->tx_dropped = atomic_long_read(&priv->tx_dropped);
|
||||
stats->rx_bytes = atomic_long_read(&priv->rx_bytes);
|
||||
stats->rx_packets = atomic_long_read(&priv->rx_packets);
|
||||
stats->rx_errors = atomic_long_read(&priv->rx_errors);
|
||||
return stats;
|
||||
}
|
||||
|
||||
|
||||
static struct net_device_ops l2tp_eth_netdev_ops = {
|
||||
.ndo_init = l2tp_eth_dev_init,
|
||||
.ndo_uninit = l2tp_eth_dev_uninit,
|
||||
.ndo_start_xmit = l2tp_eth_dev_xmit,
|
||||
.ndo_get_stats64 = l2tp_eth_get_stats64,
|
||||
};
|
||||
|
||||
static void l2tp_eth_dev_setup(struct net_device *dev)
|
||||
{
|
||||
ether_setup(dev);
|
||||
dev->priv_flags &= ~IFF_TX_SKB_SHARING;
|
||||
dev->features |= NETIF_F_LLTX;
|
||||
dev->netdev_ops = &l2tp_eth_netdev_ops;
|
||||
dev->destructor = free_netdev;
|
||||
}
|
||||
|
||||
static void l2tp_eth_dev_recv(struct l2tp_session *session, struct sk_buff *skb, int data_len)
|
||||
{
|
||||
struct l2tp_eth_sess *spriv = l2tp_session_priv(session);
|
||||
struct net_device *dev = spriv->dev;
|
||||
struct l2tp_eth *priv = netdev_priv(dev);
|
||||
|
||||
if (session->debug & L2TP_MSG_DATA) {
|
||||
unsigned int length;
|
||||
|
||||
length = min(32u, skb->len);
|
||||
if (!pskb_may_pull(skb, length))
|
||||
goto error;
|
||||
|
||||
pr_debug("%s: eth recv\n", session->name);
|
||||
print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, skb->data, length);
|
||||
}
|
||||
|
||||
if (!pskb_may_pull(skb, ETH_HLEN))
|
||||
goto error;
|
||||
|
||||
secpath_reset(skb);
|
||||
|
||||
/* checksums verified by L2TP */
|
||||
skb->ip_summed = CHECKSUM_NONE;
|
||||
|
||||
skb_dst_drop(skb);
|
||||
nf_reset(skb);
|
||||
|
||||
if (dev_forward_skb(dev, skb) == NET_RX_SUCCESS) {
|
||||
atomic_long_inc(&priv->rx_packets);
|
||||
atomic_long_add(data_len, &priv->rx_bytes);
|
||||
} else {
|
||||
atomic_long_inc(&priv->rx_errors);
|
||||
}
|
||||
return;
|
||||
|
||||
error:
|
||||
atomic_long_inc(&priv->rx_errors);
|
||||
kfree_skb(skb);
|
||||
}
|
||||
|
||||
static void l2tp_eth_delete(struct l2tp_session *session)
|
||||
{
|
||||
struct l2tp_eth_sess *spriv;
|
||||
struct net_device *dev;
|
||||
|
||||
if (session) {
|
||||
spriv = l2tp_session_priv(session);
|
||||
dev = spriv->dev;
|
||||
if (dev) {
|
||||
unregister_netdev(dev);
|
||||
spriv->dev = NULL;
|
||||
module_put(THIS_MODULE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(CONFIG_L2TP_DEBUGFS) || defined(CONFIG_L2TP_DEBUGFS_MODULE)
|
||||
static void l2tp_eth_show(struct seq_file *m, void *arg)
|
||||
{
|
||||
struct l2tp_session *session = arg;
|
||||
struct l2tp_eth_sess *spriv = l2tp_session_priv(session);
|
||||
struct net_device *dev = spriv->dev;
|
||||
|
||||
seq_printf(m, " interface %s\n", dev->name);
|
||||
}
|
||||
#endif
|
||||
|
||||
static int l2tp_eth_create(struct net *net, u32 tunnel_id, u32 session_id, u32 peer_session_id, struct l2tp_session_cfg *cfg)
|
||||
{
|
||||
struct net_device *dev;
|
||||
char name[IFNAMSIZ];
|
||||
struct l2tp_tunnel *tunnel;
|
||||
struct l2tp_session *session;
|
||||
struct l2tp_eth *priv;
|
||||
struct l2tp_eth_sess *spriv;
|
||||
int rc;
|
||||
struct l2tp_eth_net *pn;
|
||||
|
||||
tunnel = l2tp_tunnel_find(net, tunnel_id);
|
||||
if (!tunnel) {
|
||||
rc = -ENODEV;
|
||||
goto out;
|
||||
}
|
||||
|
||||
session = l2tp_session_find(net, tunnel, session_id);
|
||||
if (session) {
|
||||
rc = -EEXIST;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (cfg->ifname) {
|
||||
dev = dev_get_by_name(net, cfg->ifname);
|
||||
if (dev) {
|
||||
dev_put(dev);
|
||||
rc = -EEXIST;
|
||||
goto out;
|
||||
}
|
||||
strlcpy(name, cfg->ifname, IFNAMSIZ);
|
||||
} else
|
||||
strcpy(name, L2TP_ETH_DEV_NAME);
|
||||
|
||||
session = l2tp_session_create(sizeof(*spriv), tunnel, session_id,
|
||||
peer_session_id, cfg);
|
||||
if (!session) {
|
||||
rc = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
dev = alloc_netdev(sizeof(*priv), name, l2tp_eth_dev_setup);
|
||||
if (!dev) {
|
||||
rc = -ENOMEM;
|
||||
goto out_del_session;
|
||||
}
|
||||
|
||||
dev_net_set(dev, net);
|
||||
if (session->mtu == 0)
|
||||
session->mtu = dev->mtu - session->hdr_len;
|
||||
dev->mtu = session->mtu;
|
||||
dev->needed_headroom += session->hdr_len;
|
||||
|
||||
priv = netdev_priv(dev);
|
||||
priv->dev = dev;
|
||||
priv->session = session;
|
||||
INIT_LIST_HEAD(&priv->list);
|
||||
|
||||
priv->tunnel_sock = tunnel->sock;
|
||||
session->recv_skb = l2tp_eth_dev_recv;
|
||||
session->session_close = l2tp_eth_delete;
|
||||
#if defined(CONFIG_L2TP_DEBUGFS) || defined(CONFIG_L2TP_DEBUGFS_MODULE)
|
||||
session->show = l2tp_eth_show;
|
||||
#endif
|
||||
|
||||
spriv = l2tp_session_priv(session);
|
||||
spriv->dev = dev;
|
||||
|
||||
rc = register_netdev(dev);
|
||||
if (rc < 0)
|
||||
goto out_del_dev;
|
||||
|
||||
__module_get(THIS_MODULE);
|
||||
/* Must be done after register_netdev() */
|
||||
strlcpy(session->ifname, dev->name, IFNAMSIZ);
|
||||
|
||||
dev_hold(dev);
|
||||
pn = l2tp_eth_pernet(dev_net(dev));
|
||||
spin_lock(&pn->l2tp_eth_lock);
|
||||
list_add(&priv->list, &pn->l2tp_eth_dev_list);
|
||||
spin_unlock(&pn->l2tp_eth_lock);
|
||||
|
||||
return 0;
|
||||
|
||||
out_del_dev:
|
||||
free_netdev(dev);
|
||||
spriv->dev = NULL;
|
||||
out_del_session:
|
||||
l2tp_session_delete(session);
|
||||
out:
|
||||
return rc;
|
||||
}
|
||||
|
||||
static __net_init int l2tp_eth_init_net(struct net *net)
|
||||
{
|
||||
struct l2tp_eth_net *pn = net_generic(net, l2tp_eth_net_id);
|
||||
|
||||
INIT_LIST_HEAD(&pn->l2tp_eth_dev_list);
|
||||
spin_lock_init(&pn->l2tp_eth_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct pernet_operations l2tp_eth_net_ops = {
|
||||
.init = l2tp_eth_init_net,
|
||||
.id = &l2tp_eth_net_id,
|
||||
.size = sizeof(struct l2tp_eth_net),
|
||||
};
|
||||
|
||||
|
||||
static const struct l2tp_nl_cmd_ops l2tp_eth_nl_cmd_ops = {
|
||||
.session_create = l2tp_eth_create,
|
||||
.session_delete = l2tp_session_delete,
|
||||
};
|
||||
|
||||
|
||||
static int __init l2tp_eth_init(void)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
err = l2tp_nl_register_ops(L2TP_PWTYPE_ETH, &l2tp_eth_nl_cmd_ops);
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
err = register_pernet_device(&l2tp_eth_net_ops);
|
||||
if (err)
|
||||
goto out_unreg;
|
||||
|
||||
pr_info("L2TP ethernet pseudowire support (L2TPv3)\n");
|
||||
|
||||
return 0;
|
||||
|
||||
out_unreg:
|
||||
l2tp_nl_unregister_ops(L2TP_PWTYPE_ETH);
|
||||
out:
|
||||
return err;
|
||||
}
|
||||
|
||||
static void __exit l2tp_eth_exit(void)
|
||||
{
|
||||
unregister_pernet_device(&l2tp_eth_net_ops);
|
||||
l2tp_nl_unregister_ops(L2TP_PWTYPE_ETH);
|
||||
}
|
||||
|
||||
module_init(l2tp_eth_init);
|
||||
module_exit(l2tp_eth_exit);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("James Chapman <jchapman@katalix.com>");
|
||||
MODULE_DESCRIPTION("L2TP ethernet pseudowire driver");
|
||||
MODULE_VERSION("1.0");
|
658
net/l2tp/l2tp_ip.c
Normal file
658
net/l2tp/l2tp_ip.c
Normal file
|
@ -0,0 +1,658 @@
|
|||
/*
|
||||
* L2TPv3 IP encapsulation support
|
||||
*
|
||||
* Copyright (c) 2008,2009,2010 Katalix Systems Ltd
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/icmp.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/skbuff.h>
|
||||
#include <linux/random.h>
|
||||
#include <linux/socket.h>
|
||||
#include <linux/l2tp.h>
|
||||
#include <linux/in.h>
|
||||
#include <net/sock.h>
|
||||
#include <net/ip.h>
|
||||
#include <net/icmp.h>
|
||||
#include <net/udp.h>
|
||||
#include <net/inet_common.h>
|
||||
#include <net/inet_hashtables.h>
|
||||
#include <net/tcp_states.h>
|
||||
#include <net/protocol.h>
|
||||
#include <net/xfrm.h>
|
||||
|
||||
#include "l2tp_core.h"
|
||||
|
||||
struct l2tp_ip_sock {
|
||||
/* inet_sock has to be the first member of l2tp_ip_sock */
|
||||
struct inet_sock inet;
|
||||
|
||||
u32 conn_id;
|
||||
u32 peer_conn_id;
|
||||
};
|
||||
|
||||
static DEFINE_RWLOCK(l2tp_ip_lock);
|
||||
static struct hlist_head l2tp_ip_table;
|
||||
static struct hlist_head l2tp_ip_bind_table;
|
||||
|
||||
static inline struct l2tp_ip_sock *l2tp_ip_sk(const struct sock *sk)
|
||||
{
|
||||
return (struct l2tp_ip_sock *)sk;
|
||||
}
|
||||
|
||||
static struct sock *__l2tp_ip_bind_lookup(struct net *net, __be32 laddr, int dif, u32 tunnel_id)
|
||||
{
|
||||
struct sock *sk;
|
||||
|
||||
sk_for_each_bound(sk, &l2tp_ip_bind_table) {
|
||||
struct inet_sock *inet = inet_sk(sk);
|
||||
struct l2tp_ip_sock *l2tp = l2tp_ip_sk(sk);
|
||||
|
||||
if (l2tp == NULL)
|
||||
continue;
|
||||
|
||||
if ((l2tp->conn_id == tunnel_id) &&
|
||||
net_eq(sock_net(sk), net) &&
|
||||
!(inet->inet_rcv_saddr && inet->inet_rcv_saddr != laddr) &&
|
||||
!(sk->sk_bound_dev_if && sk->sk_bound_dev_if != dif))
|
||||
goto found;
|
||||
}
|
||||
|
||||
sk = NULL;
|
||||
found:
|
||||
return sk;
|
||||
}
|
||||
|
||||
static inline struct sock *l2tp_ip_bind_lookup(struct net *net, __be32 laddr, int dif, u32 tunnel_id)
|
||||
{
|
||||
struct sock *sk = __l2tp_ip_bind_lookup(net, laddr, dif, tunnel_id);
|
||||
if (sk)
|
||||
sock_hold(sk);
|
||||
|
||||
return sk;
|
||||
}
|
||||
|
||||
/* When processing receive frames, there are two cases to
|
||||
* consider. Data frames consist of a non-zero session-id and an
|
||||
* optional cookie. Control frames consist of a regular L2TP header
|
||||
* preceded by 32-bits of zeros.
|
||||
*
|
||||
* 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 Control Message 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
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* | (32 bits of zeros) |
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* |T|L|x|x|S|x|x|x|x|x|x|x| Ver | Length |
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* | Control Connection ID |
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* | Ns | Nr |
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
*
|
||||
* All control frames are passed to userspace.
|
||||
*/
|
||||
static int l2tp_ip_recv(struct sk_buff *skb)
|
||||
{
|
||||
struct net *net = dev_net(skb->dev);
|
||||
struct sock *sk;
|
||||
u32 session_id;
|
||||
u32 tunnel_id;
|
||||
unsigned char *ptr, *optr;
|
||||
struct l2tp_session *session;
|
||||
struct l2tp_tunnel *tunnel = NULL;
|
||||
int length;
|
||||
|
||||
/* Point to L2TP header */
|
||||
optr = ptr = skb->data;
|
||||
|
||||
if (!pskb_may_pull(skb, 4))
|
||||
goto discard;
|
||||
|
||||
session_id = ntohl(*((__be32 *) ptr));
|
||||
ptr += 4;
|
||||
|
||||
/* RFC3931: L2TP/IP packets have the first 4 bytes containing
|
||||
* the session_id. If it is 0, the packet is a L2TP control
|
||||
* frame and the session_id value can be discarded.
|
||||
*/
|
||||
if (session_id == 0) {
|
||||
__skb_pull(skb, 4);
|
||||
goto pass_up;
|
||||
}
|
||||
|
||||
/* Ok, this is a data packet. Lookup the session. */
|
||||
session = l2tp_session_find(net, NULL, session_id);
|
||||
if (session == NULL)
|
||||
goto discard;
|
||||
|
||||
tunnel = session->tunnel;
|
||||
if (tunnel == NULL)
|
||||
goto discard;
|
||||
|
||||
/* Trace packet contents, if enabled */
|
||||
if (tunnel->debug & L2TP_MSG_DATA) {
|
||||
length = min(32u, skb->len);
|
||||
if (!pskb_may_pull(skb, length))
|
||||
goto discard;
|
||||
|
||||
pr_debug("%s: ip recv\n", tunnel->name);
|
||||
print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, ptr, length);
|
||||
}
|
||||
|
||||
l2tp_recv_common(session, skb, ptr, optr, 0, skb->len, tunnel->recv_payload_hook);
|
||||
|
||||
return 0;
|
||||
|
||||
pass_up:
|
||||
/* Get the tunnel_id from the L2TP header */
|
||||
if (!pskb_may_pull(skb, 12))
|
||||
goto discard;
|
||||
|
||||
if ((skb->data[0] & 0xc0) != 0xc0)
|
||||
goto discard;
|
||||
|
||||
tunnel_id = ntohl(*(__be32 *) &skb->data[4]);
|
||||
tunnel = l2tp_tunnel_find(net, tunnel_id);
|
||||
if (tunnel != NULL)
|
||||
sk = tunnel->sock;
|
||||
else {
|
||||
struct iphdr *iph = (struct iphdr *) skb_network_header(skb);
|
||||
|
||||
read_lock_bh(&l2tp_ip_lock);
|
||||
sk = __l2tp_ip_bind_lookup(net, iph->daddr, 0, tunnel_id);
|
||||
read_unlock_bh(&l2tp_ip_lock);
|
||||
}
|
||||
|
||||
if (sk == NULL)
|
||||
goto discard;
|
||||
|
||||
sock_hold(sk);
|
||||
|
||||
if (!xfrm4_policy_check(sk, XFRM_POLICY_IN, skb))
|
||||
goto discard_put;
|
||||
|
||||
nf_reset(skb);
|
||||
|
||||
return sk_receive_skb(sk, skb, 1);
|
||||
|
||||
discard_put:
|
||||
sock_put(sk);
|
||||
|
||||
discard:
|
||||
kfree_skb(skb);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l2tp_ip_open(struct sock *sk)
|
||||
{
|
||||
/* Prevent autobind. We don't have ports. */
|
||||
inet_sk(sk)->inet_num = IPPROTO_L2TP;
|
||||
|
||||
write_lock_bh(&l2tp_ip_lock);
|
||||
sk_add_node(sk, &l2tp_ip_table);
|
||||
write_unlock_bh(&l2tp_ip_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void l2tp_ip_close(struct sock *sk, long timeout)
|
||||
{
|
||||
write_lock_bh(&l2tp_ip_lock);
|
||||
hlist_del_init(&sk->sk_bind_node);
|
||||
sk_del_node_init(sk);
|
||||
write_unlock_bh(&l2tp_ip_lock);
|
||||
sk_common_release(sk);
|
||||
}
|
||||
|
||||
static void l2tp_ip_destroy_sock(struct sock *sk)
|
||||
{
|
||||
struct sk_buff *skb;
|
||||
struct l2tp_tunnel *tunnel = l2tp_sock_to_tunnel(sk);
|
||||
|
||||
while ((skb = __skb_dequeue_tail(&sk->sk_write_queue)) != NULL)
|
||||
kfree_skb(skb);
|
||||
|
||||
if (tunnel) {
|
||||
l2tp_tunnel_closeall(tunnel);
|
||||
sock_put(sk);
|
||||
}
|
||||
|
||||
sk_refcnt_debug_dec(sk);
|
||||
}
|
||||
|
||||
static int l2tp_ip_bind(struct sock *sk, struct sockaddr *uaddr, int addr_len)
|
||||
{
|
||||
struct inet_sock *inet = inet_sk(sk);
|
||||
struct sockaddr_l2tpip *addr = (struct sockaddr_l2tpip *) uaddr;
|
||||
struct net *net = sock_net(sk);
|
||||
int ret;
|
||||
int chk_addr_ret;
|
||||
|
||||
if (!sock_flag(sk, SOCK_ZAPPED))
|
||||
return -EINVAL;
|
||||
if (addr_len < sizeof(struct sockaddr_l2tpip))
|
||||
return -EINVAL;
|
||||
if (addr->l2tp_family != AF_INET)
|
||||
return -EINVAL;
|
||||
|
||||
ret = -EADDRINUSE;
|
||||
read_lock_bh(&l2tp_ip_lock);
|
||||
if (__l2tp_ip_bind_lookup(net, addr->l2tp_addr.s_addr,
|
||||
sk->sk_bound_dev_if, addr->l2tp_conn_id))
|
||||
goto out_in_use;
|
||||
|
||||
read_unlock_bh(&l2tp_ip_lock);
|
||||
|
||||
lock_sock(sk);
|
||||
if (sk->sk_state != TCP_CLOSE || addr_len < sizeof(struct sockaddr_l2tpip))
|
||||
goto out;
|
||||
|
||||
chk_addr_ret = inet_addr_type(net, addr->l2tp_addr.s_addr);
|
||||
ret = -EADDRNOTAVAIL;
|
||||
if (addr->l2tp_addr.s_addr && chk_addr_ret != RTN_LOCAL &&
|
||||
chk_addr_ret != RTN_MULTICAST && chk_addr_ret != RTN_BROADCAST)
|
||||
goto out;
|
||||
|
||||
if (addr->l2tp_addr.s_addr)
|
||||
inet->inet_rcv_saddr = inet->inet_saddr = addr->l2tp_addr.s_addr;
|
||||
if (chk_addr_ret == RTN_MULTICAST || chk_addr_ret == RTN_BROADCAST)
|
||||
inet->inet_saddr = 0; /* Use device */
|
||||
sk_dst_reset(sk);
|
||||
|
||||
l2tp_ip_sk(sk)->conn_id = addr->l2tp_conn_id;
|
||||
|
||||
write_lock_bh(&l2tp_ip_lock);
|
||||
sk_add_bind_node(sk, &l2tp_ip_bind_table);
|
||||
sk_del_node_init(sk);
|
||||
write_unlock_bh(&l2tp_ip_lock);
|
||||
ret = 0;
|
||||
sock_reset_flag(sk, SOCK_ZAPPED);
|
||||
|
||||
out:
|
||||
release_sock(sk);
|
||||
|
||||
return ret;
|
||||
|
||||
out_in_use:
|
||||
read_unlock_bh(&l2tp_ip_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int l2tp_ip_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
|
||||
{
|
||||
struct sockaddr_l2tpip *lsa = (struct sockaddr_l2tpip *) uaddr;
|
||||
int rc;
|
||||
|
||||
if (sock_flag(sk, SOCK_ZAPPED)) /* Must bind first - autobinding does not work */
|
||||
return -EINVAL;
|
||||
|
||||
if (addr_len < sizeof(*lsa))
|
||||
return -EINVAL;
|
||||
|
||||
if (ipv4_is_multicast(lsa->l2tp_addr.s_addr))
|
||||
return -EINVAL;
|
||||
|
||||
rc = ip4_datagram_connect(sk, uaddr, addr_len);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
lock_sock(sk);
|
||||
|
||||
l2tp_ip_sk(sk)->peer_conn_id = lsa->l2tp_conn_id;
|
||||
|
||||
write_lock_bh(&l2tp_ip_lock);
|
||||
hlist_del_init(&sk->sk_bind_node);
|
||||
sk_add_bind_node(sk, &l2tp_ip_bind_table);
|
||||
write_unlock_bh(&l2tp_ip_lock);
|
||||
|
||||
release_sock(sk);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int l2tp_ip_disconnect(struct sock *sk, int flags)
|
||||
{
|
||||
if (sock_flag(sk, SOCK_ZAPPED))
|
||||
return 0;
|
||||
|
||||
return udp_disconnect(sk, flags);
|
||||
}
|
||||
|
||||
static int l2tp_ip_getname(struct socket *sock, struct sockaddr *uaddr,
|
||||
int *uaddr_len, int peer)
|
||||
{
|
||||
struct sock *sk = sock->sk;
|
||||
struct inet_sock *inet = inet_sk(sk);
|
||||
struct l2tp_ip_sock *lsk = l2tp_ip_sk(sk);
|
||||
struct sockaddr_l2tpip *lsa = (struct sockaddr_l2tpip *)uaddr;
|
||||
|
||||
memset(lsa, 0, sizeof(*lsa));
|
||||
lsa->l2tp_family = AF_INET;
|
||||
if (peer) {
|
||||
if (!inet->inet_dport)
|
||||
return -ENOTCONN;
|
||||
lsa->l2tp_conn_id = lsk->peer_conn_id;
|
||||
lsa->l2tp_addr.s_addr = inet->inet_daddr;
|
||||
} else {
|
||||
__be32 addr = inet->inet_rcv_saddr;
|
||||
if (!addr)
|
||||
addr = inet->inet_saddr;
|
||||
lsa->l2tp_conn_id = lsk->conn_id;
|
||||
lsa->l2tp_addr.s_addr = addr;
|
||||
}
|
||||
*uaddr_len = sizeof(*lsa);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l2tp_ip_backlog_recv(struct sock *sk, struct sk_buff *skb)
|
||||
{
|
||||
int rc;
|
||||
|
||||
/* Charge it to the socket, dropping if the queue is full. */
|
||||
rc = sock_queue_rcv_skb(sk, skb);
|
||||
if (rc < 0)
|
||||
goto drop;
|
||||
|
||||
return 0;
|
||||
|
||||
drop:
|
||||
IP_INC_STATS(sock_net(sk), IPSTATS_MIB_INDISCARDS);
|
||||
kfree_skb(skb);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Userspace will call sendmsg() on the tunnel socket to send L2TP
|
||||
* control frames.
|
||||
*/
|
||||
static int l2tp_ip_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg, size_t len)
|
||||
{
|
||||
struct sk_buff *skb;
|
||||
int rc;
|
||||
struct inet_sock *inet = inet_sk(sk);
|
||||
struct rtable *rt = NULL;
|
||||
struct flowi4 *fl4;
|
||||
int connected = 0;
|
||||
__be32 daddr;
|
||||
|
||||
lock_sock(sk);
|
||||
|
||||
rc = -ENOTCONN;
|
||||
if (sock_flag(sk, SOCK_DEAD))
|
||||
goto out;
|
||||
|
||||
/* Get and verify the address. */
|
||||
if (msg->msg_name) {
|
||||
struct sockaddr_l2tpip *lip = (struct sockaddr_l2tpip *) msg->msg_name;
|
||||
rc = -EINVAL;
|
||||
if (msg->msg_namelen < sizeof(*lip))
|
||||
goto out;
|
||||
|
||||
if (lip->l2tp_family != AF_INET) {
|
||||
rc = -EAFNOSUPPORT;
|
||||
if (lip->l2tp_family != AF_UNSPEC)
|
||||
goto out;
|
||||
}
|
||||
|
||||
daddr = lip->l2tp_addr.s_addr;
|
||||
} else {
|
||||
rc = -EDESTADDRREQ;
|
||||
if (sk->sk_state != TCP_ESTABLISHED)
|
||||
goto out;
|
||||
|
||||
daddr = inet->inet_daddr;
|
||||
connected = 1;
|
||||
}
|
||||
|
||||
/* Allocate a socket buffer */
|
||||
rc = -ENOMEM;
|
||||
skb = sock_wmalloc(sk, 2 + NET_SKB_PAD + sizeof(struct iphdr) +
|
||||
4 + len, 0, GFP_KERNEL);
|
||||
if (!skb)
|
||||
goto error;
|
||||
|
||||
/* Reserve space for headers, putting IP header on 4-byte boundary. */
|
||||
skb_reserve(skb, 2 + NET_SKB_PAD);
|
||||
skb_reset_network_header(skb);
|
||||
skb_reserve(skb, sizeof(struct iphdr));
|
||||
skb_reset_transport_header(skb);
|
||||
|
||||
/* Insert 0 session_id */
|
||||
*((__be32 *) skb_put(skb, 4)) = 0;
|
||||
|
||||
/* Copy user data into skb */
|
||||
rc = memcpy_fromiovec(skb_put(skb, len), msg->msg_iov, len);
|
||||
if (rc < 0) {
|
||||
kfree_skb(skb);
|
||||
goto error;
|
||||
}
|
||||
|
||||
fl4 = &inet->cork.fl.u.ip4;
|
||||
if (connected)
|
||||
rt = (struct rtable *) __sk_dst_check(sk, 0);
|
||||
|
||||
rcu_read_lock();
|
||||
if (rt == NULL) {
|
||||
const struct ip_options_rcu *inet_opt;
|
||||
|
||||
inet_opt = rcu_dereference(inet->inet_opt);
|
||||
|
||||
/* Use correct destination address if we have options. */
|
||||
if (inet_opt && inet_opt->opt.srr)
|
||||
daddr = inet_opt->opt.faddr;
|
||||
|
||||
/* If this fails, retransmit mechanism of transport layer will
|
||||
* keep trying until route appears or the connection times
|
||||
* itself out.
|
||||
*/
|
||||
rt = ip_route_output_ports(sock_net(sk), fl4, sk,
|
||||
daddr, inet->inet_saddr,
|
||||
inet->inet_dport, inet->inet_sport,
|
||||
sk->sk_protocol, RT_CONN_FLAGS(sk),
|
||||
sk->sk_bound_dev_if);
|
||||
if (IS_ERR(rt))
|
||||
goto no_route;
|
||||
if (connected) {
|
||||
sk_setup_caps(sk, &rt->dst);
|
||||
} else {
|
||||
skb_dst_set(skb, &rt->dst);
|
||||
goto xmit;
|
||||
}
|
||||
}
|
||||
|
||||
/* We dont need to clone dst here, it is guaranteed to not disappear.
|
||||
* __dev_xmit_skb() might force a refcount if needed.
|
||||
*/
|
||||
skb_dst_set_noref(skb, &rt->dst);
|
||||
|
||||
xmit:
|
||||
/* Queue the packet to IP for output */
|
||||
rc = ip_queue_xmit(skb, &inet->cork.fl);
|
||||
rcu_read_unlock();
|
||||
|
||||
error:
|
||||
if (rc >= 0)
|
||||
rc = len;
|
||||
|
||||
out:
|
||||
release_sock(sk);
|
||||
return rc;
|
||||
|
||||
no_route:
|
||||
rcu_read_unlock();
|
||||
IP_INC_STATS(sock_net(sk), IPSTATS_MIB_OUTNOROUTES);
|
||||
kfree_skb(skb);
|
||||
rc = -EHOSTUNREACH;
|
||||
goto out;
|
||||
}
|
||||
|
||||
static int l2tp_ip_recvmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
|
||||
size_t len, int noblock, int flags, int *addr_len)
|
||||
{
|
||||
struct inet_sock *inet = inet_sk(sk);
|
||||
size_t copied = 0;
|
||||
int err = -EOPNOTSUPP;
|
||||
struct sockaddr_in *sin = (struct sockaddr_in *)msg->msg_name;
|
||||
struct sk_buff *skb;
|
||||
|
||||
if (flags & MSG_OOB)
|
||||
goto out;
|
||||
|
||||
skb = skb_recv_datagram(sk, flags, noblock, &err);
|
||||
if (!skb)
|
||||
goto out;
|
||||
|
||||
copied = skb->len;
|
||||
if (len < copied) {
|
||||
msg->msg_flags |= MSG_TRUNC;
|
||||
copied = len;
|
||||
}
|
||||
|
||||
err = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied);
|
||||
if (err)
|
||||
goto done;
|
||||
|
||||
sock_recv_timestamp(msg, sk, skb);
|
||||
|
||||
/* Copy the address. */
|
||||
if (sin) {
|
||||
sin->sin_family = AF_INET;
|
||||
sin->sin_addr.s_addr = ip_hdr(skb)->saddr;
|
||||
sin->sin_port = 0;
|
||||
memset(&sin->sin_zero, 0, sizeof(sin->sin_zero));
|
||||
*addr_len = sizeof(*sin);
|
||||
}
|
||||
if (inet->cmsg_flags)
|
||||
ip_cmsg_recv(msg, skb);
|
||||
if (flags & MSG_TRUNC)
|
||||
copied = skb->len;
|
||||
done:
|
||||
skb_free_datagram(sk, skb);
|
||||
out:
|
||||
return err ? err : copied;
|
||||
}
|
||||
|
||||
static struct proto l2tp_ip_prot = {
|
||||
.name = "L2TP/IP",
|
||||
.owner = THIS_MODULE,
|
||||
.init = l2tp_ip_open,
|
||||
.close = l2tp_ip_close,
|
||||
.bind = l2tp_ip_bind,
|
||||
.connect = l2tp_ip_connect,
|
||||
.disconnect = l2tp_ip_disconnect,
|
||||
.ioctl = udp_ioctl,
|
||||
.destroy = l2tp_ip_destroy_sock,
|
||||
.setsockopt = ip_setsockopt,
|
||||
.getsockopt = ip_getsockopt,
|
||||
.sendmsg = l2tp_ip_sendmsg,
|
||||
.recvmsg = l2tp_ip_recvmsg,
|
||||
.backlog_rcv = l2tp_ip_backlog_recv,
|
||||
.hash = inet_hash,
|
||||
.unhash = inet_unhash,
|
||||
.obj_size = sizeof(struct l2tp_ip_sock),
|
||||
#ifdef CONFIG_COMPAT
|
||||
.compat_setsockopt = compat_ip_setsockopt,
|
||||
.compat_getsockopt = compat_ip_getsockopt,
|
||||
#endif
|
||||
};
|
||||
|
||||
static const struct proto_ops l2tp_ip_ops = {
|
||||
.family = PF_INET,
|
||||
.owner = THIS_MODULE,
|
||||
.release = inet_release,
|
||||
.bind = inet_bind,
|
||||
.connect = inet_dgram_connect,
|
||||
.socketpair = sock_no_socketpair,
|
||||
.accept = sock_no_accept,
|
||||
.getname = l2tp_ip_getname,
|
||||
.poll = datagram_poll,
|
||||
.ioctl = inet_ioctl,
|
||||
.listen = sock_no_listen,
|
||||
.shutdown = inet_shutdown,
|
||||
.setsockopt = sock_common_setsockopt,
|
||||
.getsockopt = sock_common_getsockopt,
|
||||
.sendmsg = inet_sendmsg,
|
||||
.recvmsg = sock_common_recvmsg,
|
||||
.mmap = sock_no_mmap,
|
||||
.sendpage = sock_no_sendpage,
|
||||
#ifdef CONFIG_COMPAT
|
||||
.compat_setsockopt = compat_sock_common_setsockopt,
|
||||
.compat_getsockopt = compat_sock_common_getsockopt,
|
||||
#endif
|
||||
};
|
||||
|
||||
static struct inet_protosw l2tp_ip_protosw = {
|
||||
.type = SOCK_DGRAM,
|
||||
.protocol = IPPROTO_L2TP,
|
||||
.prot = &l2tp_ip_prot,
|
||||
.ops = &l2tp_ip_ops,
|
||||
.no_check = 0,
|
||||
};
|
||||
|
||||
static struct net_protocol l2tp_ip_protocol __read_mostly = {
|
||||
.handler = l2tp_ip_recv,
|
||||
.netns_ok = 1,
|
||||
};
|
||||
|
||||
static int __init l2tp_ip_init(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
pr_info("L2TP IP encapsulation support (L2TPv3)\n");
|
||||
|
||||
err = proto_register(&l2tp_ip_prot, 1);
|
||||
if (err != 0)
|
||||
goto out;
|
||||
|
||||
err = inet_add_protocol(&l2tp_ip_protocol, IPPROTO_L2TP);
|
||||
if (err)
|
||||
goto out1;
|
||||
|
||||
inet_register_protosw(&l2tp_ip_protosw);
|
||||
return 0;
|
||||
|
||||
out1:
|
||||
proto_unregister(&l2tp_ip_prot);
|
||||
out:
|
||||
return err;
|
||||
}
|
||||
|
||||
static void __exit l2tp_ip_exit(void)
|
||||
{
|
||||
inet_unregister_protosw(&l2tp_ip_protosw);
|
||||
inet_del_protocol(&l2tp_ip_protocol, IPPROTO_L2TP);
|
||||
proto_unregister(&l2tp_ip_prot);
|
||||
}
|
||||
|
||||
module_init(l2tp_ip_init);
|
||||
module_exit(l2tp_ip_exit);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("James Chapman <jchapman@katalix.com>");
|
||||
MODULE_DESCRIPTION("L2TP over IP");
|
||||
MODULE_VERSION("1.0");
|
||||
|
||||
/* Use the value of SOCK_DGRAM (2) directory, because __stringify doesn't like
|
||||
* enums
|
||||
*/
|
||||
MODULE_ALIAS_NET_PF_PROTO_TYPE(PF_INET, 2, IPPROTO_L2TP);
|
811
net/l2tp/l2tp_ip6.c
Normal file
811
net/l2tp/l2tp_ip6.c
Normal file
|
@ -0,0 +1,811 @@
|
|||
/*
|
||||
* L2TPv3 IP encapsulation support for IPv6
|
||||
*
|
||||
* Copyright (c) 2012 Katalix Systems Ltd
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/icmp.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/skbuff.h>
|
||||
#include <linux/random.h>
|
||||
#include <linux/socket.h>
|
||||
#include <linux/l2tp.h>
|
||||
#include <linux/in.h>
|
||||
#include <linux/in6.h>
|
||||
#include <net/sock.h>
|
||||
#include <net/ip.h>
|
||||
#include <net/icmp.h>
|
||||
#include <net/udp.h>
|
||||
#include <net/inet_common.h>
|
||||
#include <net/inet_hashtables.h>
|
||||
#include <net/tcp_states.h>
|
||||
#include <net/protocol.h>
|
||||
#include <net/xfrm.h>
|
||||
|
||||
#include <net/transp_v6.h>
|
||||
#include <net/addrconf.h>
|
||||
#include <net/ip6_route.h>
|
||||
|
||||
#include "l2tp_core.h"
|
||||
|
||||
struct l2tp_ip6_sock {
|
||||
/* inet_sock has to be the first member of l2tp_ip6_sock */
|
||||
struct inet_sock inet;
|
||||
|
||||
u32 conn_id;
|
||||
u32 peer_conn_id;
|
||||
|
||||
/* ipv6_pinfo has to be the last member of l2tp_ip6_sock, see
|
||||
inet6_sk_generic */
|
||||
struct ipv6_pinfo inet6;
|
||||
};
|
||||
|
||||
static DEFINE_RWLOCK(l2tp_ip6_lock);
|
||||
static struct hlist_head l2tp_ip6_table;
|
||||
static struct hlist_head l2tp_ip6_bind_table;
|
||||
|
||||
static inline struct l2tp_ip6_sock *l2tp_ip6_sk(const struct sock *sk)
|
||||
{
|
||||
return (struct l2tp_ip6_sock *)sk;
|
||||
}
|
||||
|
||||
static struct sock *__l2tp_ip6_bind_lookup(struct net *net,
|
||||
struct in6_addr *laddr,
|
||||
int dif, u32 tunnel_id)
|
||||
{
|
||||
struct sock *sk;
|
||||
|
||||
sk_for_each_bound(sk, &l2tp_ip6_bind_table) {
|
||||
struct in6_addr *addr = inet6_rcv_saddr(sk);
|
||||
struct l2tp_ip6_sock *l2tp = l2tp_ip6_sk(sk);
|
||||
|
||||
if (l2tp == NULL)
|
||||
continue;
|
||||
|
||||
if ((l2tp->conn_id == tunnel_id) &&
|
||||
net_eq(sock_net(sk), net) &&
|
||||
!(addr && ipv6_addr_equal(addr, laddr)) &&
|
||||
!(sk->sk_bound_dev_if && sk->sk_bound_dev_if != dif))
|
||||
goto found;
|
||||
}
|
||||
|
||||
sk = NULL;
|
||||
found:
|
||||
return sk;
|
||||
}
|
||||
|
||||
static inline struct sock *l2tp_ip6_bind_lookup(struct net *net,
|
||||
struct in6_addr *laddr,
|
||||
int dif, u32 tunnel_id)
|
||||
{
|
||||
struct sock *sk = __l2tp_ip6_bind_lookup(net, laddr, dif, tunnel_id);
|
||||
if (sk)
|
||||
sock_hold(sk);
|
||||
|
||||
return sk;
|
||||
}
|
||||
|
||||
/* When processing receive frames, there are two cases to
|
||||
* consider. Data frames consist of a non-zero session-id and an
|
||||
* optional cookie. Control frames consist of a regular L2TP header
|
||||
* preceded by 32-bits of zeros.
|
||||
*
|
||||
* 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 Control Message 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
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* | (32 bits of zeros) |
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* |T|L|x|x|S|x|x|x|x|x|x|x| Ver | Length |
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* | Control Connection ID |
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* | Ns | Nr |
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
*
|
||||
* All control frames are passed to userspace.
|
||||
*/
|
||||
static int l2tp_ip6_recv(struct sk_buff *skb)
|
||||
{
|
||||
struct sock *sk;
|
||||
u32 session_id;
|
||||
u32 tunnel_id;
|
||||
unsigned char *ptr, *optr;
|
||||
struct l2tp_session *session;
|
||||
struct l2tp_tunnel *tunnel = NULL;
|
||||
int length;
|
||||
|
||||
/* Point to L2TP header */
|
||||
optr = ptr = skb->data;
|
||||
|
||||
if (!pskb_may_pull(skb, 4))
|
||||
goto discard;
|
||||
|
||||
session_id = ntohl(*((__be32 *) ptr));
|
||||
ptr += 4;
|
||||
|
||||
/* RFC3931: L2TP/IP packets have the first 4 bytes containing
|
||||
* the session_id. If it is 0, the packet is a L2TP control
|
||||
* frame and the session_id value can be discarded.
|
||||
*/
|
||||
if (session_id == 0) {
|
||||
__skb_pull(skb, 4);
|
||||
goto pass_up;
|
||||
}
|
||||
|
||||
/* Ok, this is a data packet. Lookup the session. */
|
||||
session = l2tp_session_find(&init_net, NULL, session_id);
|
||||
if (session == NULL)
|
||||
goto discard;
|
||||
|
||||
tunnel = session->tunnel;
|
||||
if (tunnel == NULL)
|
||||
goto discard;
|
||||
|
||||
/* Trace packet contents, if enabled */
|
||||
if (tunnel->debug & L2TP_MSG_DATA) {
|
||||
length = min(32u, skb->len);
|
||||
if (!pskb_may_pull(skb, length))
|
||||
goto discard;
|
||||
|
||||
pr_debug("%s: ip recv\n", tunnel->name);
|
||||
print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, ptr, length);
|
||||
}
|
||||
|
||||
l2tp_recv_common(session, skb, ptr, optr, 0, skb->len,
|
||||
tunnel->recv_payload_hook);
|
||||
return 0;
|
||||
|
||||
pass_up:
|
||||
/* Get the tunnel_id from the L2TP header */
|
||||
if (!pskb_may_pull(skb, 12))
|
||||
goto discard;
|
||||
|
||||
if ((skb->data[0] & 0xc0) != 0xc0)
|
||||
goto discard;
|
||||
|
||||
tunnel_id = ntohl(*(__be32 *) &skb->data[4]);
|
||||
tunnel = l2tp_tunnel_find(&init_net, tunnel_id);
|
||||
if (tunnel != NULL)
|
||||
sk = tunnel->sock;
|
||||
else {
|
||||
struct ipv6hdr *iph = ipv6_hdr(skb);
|
||||
|
||||
read_lock_bh(&l2tp_ip6_lock);
|
||||
sk = __l2tp_ip6_bind_lookup(&init_net, &iph->daddr,
|
||||
0, tunnel_id);
|
||||
read_unlock_bh(&l2tp_ip6_lock);
|
||||
}
|
||||
|
||||
if (sk == NULL)
|
||||
goto discard;
|
||||
|
||||
sock_hold(sk);
|
||||
|
||||
if (!xfrm6_policy_check(sk, XFRM_POLICY_IN, skb))
|
||||
goto discard_put;
|
||||
|
||||
nf_reset(skb);
|
||||
|
||||
return sk_receive_skb(sk, skb, 1);
|
||||
|
||||
discard_put:
|
||||
sock_put(sk);
|
||||
|
||||
discard:
|
||||
kfree_skb(skb);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l2tp_ip6_open(struct sock *sk)
|
||||
{
|
||||
/* Prevent autobind. We don't have ports. */
|
||||
inet_sk(sk)->inet_num = IPPROTO_L2TP;
|
||||
|
||||
write_lock_bh(&l2tp_ip6_lock);
|
||||
sk_add_node(sk, &l2tp_ip6_table);
|
||||
write_unlock_bh(&l2tp_ip6_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void l2tp_ip6_close(struct sock *sk, long timeout)
|
||||
{
|
||||
write_lock_bh(&l2tp_ip6_lock);
|
||||
hlist_del_init(&sk->sk_bind_node);
|
||||
sk_del_node_init(sk);
|
||||
write_unlock_bh(&l2tp_ip6_lock);
|
||||
|
||||
sk_common_release(sk);
|
||||
}
|
||||
|
||||
static void l2tp_ip6_destroy_sock(struct sock *sk)
|
||||
{
|
||||
struct l2tp_tunnel *tunnel = l2tp_sock_to_tunnel(sk);
|
||||
|
||||
lock_sock(sk);
|
||||
ip6_flush_pending_frames(sk);
|
||||
release_sock(sk);
|
||||
|
||||
if (tunnel) {
|
||||
l2tp_tunnel_closeall(tunnel);
|
||||
sock_put(sk);
|
||||
}
|
||||
|
||||
inet6_destroy_sock(sk);
|
||||
}
|
||||
|
||||
static int l2tp_ip6_bind(struct sock *sk, struct sockaddr *uaddr, int addr_len)
|
||||
{
|
||||
struct inet_sock *inet = inet_sk(sk);
|
||||
struct ipv6_pinfo *np = inet6_sk(sk);
|
||||
struct sockaddr_l2tpip6 *addr = (struct sockaddr_l2tpip6 *) uaddr;
|
||||
__be32 v4addr = 0;
|
||||
int addr_type;
|
||||
int err;
|
||||
|
||||
if (!sock_flag(sk, SOCK_ZAPPED))
|
||||
return -EINVAL;
|
||||
if (addr->l2tp_family != AF_INET6)
|
||||
return -EINVAL;
|
||||
if (addr_len < sizeof(*addr))
|
||||
return -EINVAL;
|
||||
|
||||
addr_type = ipv6_addr_type(&addr->l2tp_addr);
|
||||
|
||||
/* l2tp_ip6 sockets are IPv6 only */
|
||||
if (addr_type == IPV6_ADDR_MAPPED)
|
||||
return -EADDRNOTAVAIL;
|
||||
|
||||
/* L2TP is point-point, not multicast */
|
||||
if (addr_type & IPV6_ADDR_MULTICAST)
|
||||
return -EADDRNOTAVAIL;
|
||||
|
||||
err = -EADDRINUSE;
|
||||
read_lock_bh(&l2tp_ip6_lock);
|
||||
if (__l2tp_ip6_bind_lookup(&init_net, &addr->l2tp_addr,
|
||||
sk->sk_bound_dev_if, addr->l2tp_conn_id))
|
||||
goto out_in_use;
|
||||
read_unlock_bh(&l2tp_ip6_lock);
|
||||
|
||||
lock_sock(sk);
|
||||
|
||||
err = -EINVAL;
|
||||
if (sk->sk_state != TCP_CLOSE)
|
||||
goto out_unlock;
|
||||
|
||||
/* Check if the address belongs to the host. */
|
||||
rcu_read_lock();
|
||||
if (addr_type != IPV6_ADDR_ANY) {
|
||||
struct net_device *dev = NULL;
|
||||
|
||||
if (addr_type & IPV6_ADDR_LINKLOCAL) {
|
||||
if (addr_len >= sizeof(struct sockaddr_in6) &&
|
||||
addr->l2tp_scope_id) {
|
||||
/* Override any existing binding, if another
|
||||
* one is supplied by user.
|
||||
*/
|
||||
sk->sk_bound_dev_if = addr->l2tp_scope_id;
|
||||
}
|
||||
|
||||
/* Binding to link-local address requires an
|
||||
interface */
|
||||
if (!sk->sk_bound_dev_if)
|
||||
goto out_unlock_rcu;
|
||||
|
||||
err = -ENODEV;
|
||||
dev = dev_get_by_index_rcu(sock_net(sk),
|
||||
sk->sk_bound_dev_if);
|
||||
if (!dev)
|
||||
goto out_unlock_rcu;
|
||||
}
|
||||
|
||||
/* ipv4 addr of the socket is invalid. Only the
|
||||
* unspecified and mapped address have a v4 equivalent.
|
||||
*/
|
||||
v4addr = LOOPBACK4_IPV6;
|
||||
err = -EADDRNOTAVAIL;
|
||||
if (!ipv6_chk_addr(sock_net(sk), &addr->l2tp_addr, dev, 0))
|
||||
goto out_unlock_rcu;
|
||||
}
|
||||
rcu_read_unlock();
|
||||
|
||||
inet->inet_rcv_saddr = inet->inet_saddr = v4addr;
|
||||
np->rcv_saddr = addr->l2tp_addr;
|
||||
np->saddr = addr->l2tp_addr;
|
||||
|
||||
l2tp_ip6_sk(sk)->conn_id = addr->l2tp_conn_id;
|
||||
|
||||
write_lock_bh(&l2tp_ip6_lock);
|
||||
sk_add_bind_node(sk, &l2tp_ip6_bind_table);
|
||||
sk_del_node_init(sk);
|
||||
write_unlock_bh(&l2tp_ip6_lock);
|
||||
|
||||
sock_reset_flag(sk, SOCK_ZAPPED);
|
||||
release_sock(sk);
|
||||
return 0;
|
||||
|
||||
out_unlock_rcu:
|
||||
rcu_read_unlock();
|
||||
out_unlock:
|
||||
release_sock(sk);
|
||||
return err;
|
||||
|
||||
out_in_use:
|
||||
read_unlock_bh(&l2tp_ip6_lock);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int l2tp_ip6_connect(struct sock *sk, struct sockaddr *uaddr,
|
||||
int addr_len)
|
||||
{
|
||||
struct sockaddr_l2tpip6 *lsa = (struct sockaddr_l2tpip6 *) uaddr;
|
||||
struct sockaddr_in6 *usin = (struct sockaddr_in6 *) uaddr;
|
||||
struct in6_addr *daddr;
|
||||
int addr_type;
|
||||
int rc;
|
||||
|
||||
if (sock_flag(sk, SOCK_ZAPPED)) /* Must bind first - autobinding does not work */
|
||||
return -EINVAL;
|
||||
|
||||
if (addr_len < sizeof(*lsa))
|
||||
return -EINVAL;
|
||||
|
||||
addr_type = ipv6_addr_type(&usin->sin6_addr);
|
||||
if (addr_type & IPV6_ADDR_MULTICAST)
|
||||
return -EINVAL;
|
||||
|
||||
if (addr_type & IPV6_ADDR_MAPPED) {
|
||||
daddr = &usin->sin6_addr;
|
||||
if (ipv4_is_multicast(daddr->s6_addr32[3]))
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
rc = ip6_datagram_connect(sk, uaddr, addr_len);
|
||||
|
||||
lock_sock(sk);
|
||||
|
||||
l2tp_ip6_sk(sk)->peer_conn_id = lsa->l2tp_conn_id;
|
||||
|
||||
write_lock_bh(&l2tp_ip6_lock);
|
||||
hlist_del_init(&sk->sk_bind_node);
|
||||
sk_add_bind_node(sk, &l2tp_ip6_bind_table);
|
||||
write_unlock_bh(&l2tp_ip6_lock);
|
||||
|
||||
release_sock(sk);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int l2tp_ip6_disconnect(struct sock *sk, int flags)
|
||||
{
|
||||
if (sock_flag(sk, SOCK_ZAPPED))
|
||||
return 0;
|
||||
|
||||
return udp_disconnect(sk, flags);
|
||||
}
|
||||
|
||||
static int l2tp_ip6_getname(struct socket *sock, struct sockaddr *uaddr,
|
||||
int *uaddr_len, int peer)
|
||||
{
|
||||
struct sockaddr_l2tpip6 *lsa = (struct sockaddr_l2tpip6 *)uaddr;
|
||||
struct sock *sk = sock->sk;
|
||||
struct ipv6_pinfo *np = inet6_sk(sk);
|
||||
struct l2tp_ip6_sock *lsk = l2tp_ip6_sk(sk);
|
||||
|
||||
lsa->l2tp_family = AF_INET6;
|
||||
lsa->l2tp_flowinfo = 0;
|
||||
lsa->l2tp_scope_id = 0;
|
||||
lsa->l2tp_unused = 0;
|
||||
if (peer) {
|
||||
if (!lsk->peer_conn_id)
|
||||
return -ENOTCONN;
|
||||
lsa->l2tp_conn_id = lsk->peer_conn_id;
|
||||
lsa->l2tp_addr = np->daddr;
|
||||
if (np->sndflow)
|
||||
lsa->l2tp_flowinfo = np->flow_label;
|
||||
} else {
|
||||
if (ipv6_addr_any(&np->rcv_saddr))
|
||||
lsa->l2tp_addr = np->saddr;
|
||||
else
|
||||
lsa->l2tp_addr = np->rcv_saddr;
|
||||
|
||||
lsa->l2tp_conn_id = lsk->conn_id;
|
||||
}
|
||||
if (ipv6_addr_type(&lsa->l2tp_addr) & IPV6_ADDR_LINKLOCAL)
|
||||
lsa->l2tp_scope_id = sk->sk_bound_dev_if;
|
||||
*uaddr_len = sizeof(*lsa);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l2tp_ip6_backlog_recv(struct sock *sk, struct sk_buff *skb)
|
||||
{
|
||||
int rc;
|
||||
|
||||
/* Charge it to the socket, dropping if the queue is full. */
|
||||
rc = sock_queue_rcv_skb(sk, skb);
|
||||
if (rc < 0)
|
||||
goto drop;
|
||||
|
||||
return 0;
|
||||
|
||||
drop:
|
||||
IP_INC_STATS(&init_net, IPSTATS_MIB_INDISCARDS);
|
||||
kfree_skb(skb);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int l2tp_ip6_push_pending_frames(struct sock *sk)
|
||||
{
|
||||
struct sk_buff *skb;
|
||||
__be32 *transhdr = NULL;
|
||||
int err = 0;
|
||||
|
||||
skb = skb_peek(&sk->sk_write_queue);
|
||||
if (skb == NULL)
|
||||
goto out;
|
||||
|
||||
transhdr = (__be32 *)skb_transport_header(skb);
|
||||
*transhdr = 0;
|
||||
|
||||
err = ip6_push_pending_frames(sk);
|
||||
|
||||
out:
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Userspace will call sendmsg() on the tunnel socket to send L2TP
|
||||
* control frames.
|
||||
*/
|
||||
static int l2tp_ip6_sendmsg(struct kiocb *iocb, struct sock *sk,
|
||||
struct msghdr *msg, size_t len)
|
||||
{
|
||||
struct ipv6_txoptions opt_space;
|
||||
struct sockaddr_l2tpip6 *lsa =
|
||||
(struct sockaddr_l2tpip6 *) msg->msg_name;
|
||||
struct in6_addr *daddr, *final_p, final;
|
||||
struct ipv6_pinfo *np = inet6_sk(sk);
|
||||
struct ipv6_txoptions *opt = NULL;
|
||||
struct ip6_flowlabel *flowlabel = NULL;
|
||||
struct dst_entry *dst = NULL;
|
||||
struct flowi6 fl6;
|
||||
int addr_len = msg->msg_namelen;
|
||||
int hlimit = -1;
|
||||
int tclass = -1;
|
||||
int dontfrag = -1;
|
||||
int transhdrlen = 4; /* zero session-id */
|
||||
int ulen = len + transhdrlen;
|
||||
int err;
|
||||
|
||||
/* Rough check on arithmetic overflow,
|
||||
better check is made in ip6_append_data().
|
||||
*/
|
||||
if (len > INT_MAX)
|
||||
return -EMSGSIZE;
|
||||
|
||||
/* Mirror BSD error message compatibility */
|
||||
if (msg->msg_flags & MSG_OOB)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
/*
|
||||
* Get and verify the address.
|
||||
*/
|
||||
memset(&fl6, 0, sizeof(fl6));
|
||||
|
||||
fl6.flowi6_mark = sk->sk_mark;
|
||||
|
||||
if (lsa) {
|
||||
if (addr_len < SIN6_LEN_RFC2133)
|
||||
return -EINVAL;
|
||||
|
||||
if (lsa->l2tp_family && lsa->l2tp_family != AF_INET6)
|
||||
return -EAFNOSUPPORT;
|
||||
|
||||
daddr = &lsa->l2tp_addr;
|
||||
if (np->sndflow) {
|
||||
fl6.flowlabel = lsa->l2tp_flowinfo & IPV6_FLOWINFO_MASK;
|
||||
if (fl6.flowlabel&IPV6_FLOWLABEL_MASK) {
|
||||
flowlabel = fl6_sock_lookup(sk, fl6.flowlabel);
|
||||
if (flowlabel == NULL)
|
||||
return -EINVAL;
|
||||
daddr = &flowlabel->dst;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Otherwise it will be difficult to maintain
|
||||
* sk->sk_dst_cache.
|
||||
*/
|
||||
if (sk->sk_state == TCP_ESTABLISHED &&
|
||||
ipv6_addr_equal(daddr, &np->daddr))
|
||||
daddr = &np->daddr;
|
||||
|
||||
if (addr_len >= sizeof(struct sockaddr_in6) &&
|
||||
lsa->l2tp_scope_id &&
|
||||
ipv6_addr_type(daddr) & IPV6_ADDR_LINKLOCAL)
|
||||
fl6.flowi6_oif = lsa->l2tp_scope_id;
|
||||
} else {
|
||||
if (sk->sk_state != TCP_ESTABLISHED)
|
||||
return -EDESTADDRREQ;
|
||||
|
||||
daddr = &np->daddr;
|
||||
fl6.flowlabel = np->flow_label;
|
||||
}
|
||||
|
||||
if (fl6.flowi6_oif == 0)
|
||||
fl6.flowi6_oif = sk->sk_bound_dev_if;
|
||||
|
||||
if (msg->msg_controllen) {
|
||||
opt = &opt_space;
|
||||
memset(opt, 0, sizeof(struct ipv6_txoptions));
|
||||
opt->tot_len = sizeof(struct ipv6_txoptions);
|
||||
|
||||
err = ip6_datagram_send_ctl(sock_net(sk), sk, msg, &fl6, opt,
|
||||
&hlimit, &tclass, &dontfrag);
|
||||
if (err < 0) {
|
||||
fl6_sock_release(flowlabel);
|
||||
return err;
|
||||
}
|
||||
if ((fl6.flowlabel & IPV6_FLOWLABEL_MASK) && !flowlabel) {
|
||||
flowlabel = fl6_sock_lookup(sk, fl6.flowlabel);
|
||||
if (flowlabel == NULL)
|
||||
return -EINVAL;
|
||||
}
|
||||
if (!(opt->opt_nflen|opt->opt_flen))
|
||||
opt = NULL;
|
||||
}
|
||||
|
||||
if (opt == NULL)
|
||||
opt = np->opt;
|
||||
if (flowlabel)
|
||||
opt = fl6_merge_options(&opt_space, flowlabel, opt);
|
||||
opt = ipv6_fixup_options(&opt_space, opt);
|
||||
|
||||
fl6.flowi6_proto = sk->sk_protocol;
|
||||
if (!ipv6_addr_any(daddr))
|
||||
fl6.daddr = *daddr;
|
||||
else
|
||||
fl6.daddr.s6_addr[15] = 0x1; /* :: means loopback (BSD'ism) */
|
||||
if (ipv6_addr_any(&fl6.saddr) && !ipv6_addr_any(&np->saddr))
|
||||
fl6.saddr = np->saddr;
|
||||
|
||||
final_p = fl6_update_dst(&fl6, opt, &final);
|
||||
|
||||
if (!fl6.flowi6_oif && ipv6_addr_is_multicast(&fl6.daddr))
|
||||
fl6.flowi6_oif = np->mcast_oif;
|
||||
else if (!fl6.flowi6_oif)
|
||||
fl6.flowi6_oif = np->ucast_oif;
|
||||
|
||||
security_sk_classify_flow(sk, flowi6_to_flowi(&fl6));
|
||||
|
||||
dst = ip6_dst_lookup_flow(sk, &fl6, final_p, true);
|
||||
if (IS_ERR(dst)) {
|
||||
err = PTR_ERR(dst);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (hlimit < 0) {
|
||||
if (ipv6_addr_is_multicast(&fl6.daddr))
|
||||
hlimit = np->mcast_hops;
|
||||
else
|
||||
hlimit = np->hop_limit;
|
||||
if (hlimit < 0)
|
||||
hlimit = ip6_dst_hoplimit(dst);
|
||||
}
|
||||
|
||||
if (tclass < 0)
|
||||
tclass = np->tclass;
|
||||
|
||||
if (dontfrag < 0)
|
||||
dontfrag = np->dontfrag;
|
||||
|
||||
if (msg->msg_flags & MSG_CONFIRM)
|
||||
goto do_confirm;
|
||||
|
||||
back_from_confirm:
|
||||
lock_sock(sk);
|
||||
err = ip6_append_data(sk, ip_generic_getfrag, msg->msg_iov,
|
||||
ulen, transhdrlen, hlimit, tclass, opt,
|
||||
&fl6, (struct rt6_info *)dst,
|
||||
msg->msg_flags, dontfrag);
|
||||
if (err)
|
||||
ip6_flush_pending_frames(sk);
|
||||
else if (!(msg->msg_flags & MSG_MORE))
|
||||
err = l2tp_ip6_push_pending_frames(sk);
|
||||
release_sock(sk);
|
||||
done:
|
||||
dst_release(dst);
|
||||
out:
|
||||
fl6_sock_release(flowlabel);
|
||||
|
||||
return err < 0 ? err : len;
|
||||
|
||||
do_confirm:
|
||||
dst_confirm(dst);
|
||||
if (!(msg->msg_flags & MSG_PROBE) || len)
|
||||
goto back_from_confirm;
|
||||
err = 0;
|
||||
goto done;
|
||||
}
|
||||
|
||||
static int l2tp_ip6_recvmsg(struct kiocb *iocb, struct sock *sk,
|
||||
struct msghdr *msg, size_t len, int noblock,
|
||||
int flags, int *addr_len)
|
||||
{
|
||||
struct ipv6_pinfo *np = inet6_sk(sk);
|
||||
struct sockaddr_l2tpip6 *lsa = (struct sockaddr_l2tpip6 *)msg->msg_name;
|
||||
size_t copied = 0;
|
||||
int err = -EOPNOTSUPP;
|
||||
struct sk_buff *skb;
|
||||
|
||||
if (flags & MSG_OOB)
|
||||
goto out;
|
||||
|
||||
if (addr_len)
|
||||
*addr_len = sizeof(*lsa);
|
||||
|
||||
if (flags & MSG_ERRQUEUE)
|
||||
return ipv6_recv_error(sk, msg, len, addr_len);
|
||||
|
||||
skb = skb_recv_datagram(sk, flags, noblock, &err);
|
||||
if (!skb)
|
||||
goto out;
|
||||
|
||||
copied = skb->len;
|
||||
if (len < copied) {
|
||||
msg->msg_flags |= MSG_TRUNC;
|
||||
copied = len;
|
||||
}
|
||||
|
||||
err = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied);
|
||||
if (err)
|
||||
goto done;
|
||||
|
||||
sock_recv_timestamp(msg, sk, skb);
|
||||
|
||||
/* Copy the address. */
|
||||
if (lsa) {
|
||||
lsa->l2tp_family = AF_INET6;
|
||||
lsa->l2tp_unused = 0;
|
||||
lsa->l2tp_addr = ipv6_hdr(skb)->saddr;
|
||||
lsa->l2tp_flowinfo = 0;
|
||||
lsa->l2tp_scope_id = 0;
|
||||
lsa->l2tp_conn_id = 0;
|
||||
if (ipv6_addr_type(&lsa->l2tp_addr) & IPV6_ADDR_LINKLOCAL)
|
||||
lsa->l2tp_scope_id = IP6CB(skb)->iif;
|
||||
}
|
||||
|
||||
if (np->rxopt.all)
|
||||
ip6_datagram_recv_ctl(sk, msg, skb);
|
||||
|
||||
if (flags & MSG_TRUNC)
|
||||
copied = skb->len;
|
||||
done:
|
||||
skb_free_datagram(sk, skb);
|
||||
out:
|
||||
return err ? err : copied;
|
||||
}
|
||||
|
||||
static struct proto l2tp_ip6_prot = {
|
||||
.name = "L2TP/IPv6",
|
||||
.owner = THIS_MODULE,
|
||||
.init = l2tp_ip6_open,
|
||||
.close = l2tp_ip6_close,
|
||||
.bind = l2tp_ip6_bind,
|
||||
.connect = l2tp_ip6_connect,
|
||||
.disconnect = l2tp_ip6_disconnect,
|
||||
.ioctl = udp_ioctl,
|
||||
.destroy = l2tp_ip6_destroy_sock,
|
||||
.setsockopt = ipv6_setsockopt,
|
||||
.getsockopt = ipv6_getsockopt,
|
||||
.sendmsg = l2tp_ip6_sendmsg,
|
||||
.recvmsg = l2tp_ip6_recvmsg,
|
||||
.backlog_rcv = l2tp_ip6_backlog_recv,
|
||||
.hash = inet_hash,
|
||||
.unhash = inet_unhash,
|
||||
.obj_size = sizeof(struct l2tp_ip6_sock),
|
||||
#ifdef CONFIG_COMPAT
|
||||
.compat_setsockopt = compat_ipv6_setsockopt,
|
||||
.compat_getsockopt = compat_ipv6_getsockopt,
|
||||
#endif
|
||||
};
|
||||
|
||||
static const struct proto_ops l2tp_ip6_ops = {
|
||||
.family = PF_INET6,
|
||||
.owner = THIS_MODULE,
|
||||
.release = inet6_release,
|
||||
.bind = inet6_bind,
|
||||
.connect = inet_dgram_connect,
|
||||
.socketpair = sock_no_socketpair,
|
||||
.accept = sock_no_accept,
|
||||
.getname = l2tp_ip6_getname,
|
||||
.poll = datagram_poll,
|
||||
.ioctl = inet6_ioctl,
|
||||
.listen = sock_no_listen,
|
||||
.shutdown = inet_shutdown,
|
||||
.setsockopt = sock_common_setsockopt,
|
||||
.getsockopt = sock_common_getsockopt,
|
||||
.sendmsg = inet_sendmsg,
|
||||
.recvmsg = sock_common_recvmsg,
|
||||
.mmap = sock_no_mmap,
|
||||
.sendpage = sock_no_sendpage,
|
||||
#ifdef CONFIG_COMPAT
|
||||
.compat_setsockopt = compat_sock_common_setsockopt,
|
||||
.compat_getsockopt = compat_sock_common_getsockopt,
|
||||
#endif
|
||||
};
|
||||
|
||||
static struct inet_protosw l2tp_ip6_protosw = {
|
||||
.type = SOCK_DGRAM,
|
||||
.protocol = IPPROTO_L2TP,
|
||||
.prot = &l2tp_ip6_prot,
|
||||
.ops = &l2tp_ip6_ops,
|
||||
.no_check = 0,
|
||||
};
|
||||
|
||||
static struct inet6_protocol l2tp_ip6_protocol __read_mostly = {
|
||||
.handler = l2tp_ip6_recv,
|
||||
};
|
||||
|
||||
static int __init l2tp_ip6_init(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
pr_info("L2TP IP encapsulation support for IPv6 (L2TPv3)\n");
|
||||
|
||||
err = proto_register(&l2tp_ip6_prot, 1);
|
||||
if (err != 0)
|
||||
goto out;
|
||||
|
||||
err = inet6_add_protocol(&l2tp_ip6_protocol, IPPROTO_L2TP);
|
||||
if (err)
|
||||
goto out1;
|
||||
|
||||
inet6_register_protosw(&l2tp_ip6_protosw);
|
||||
return 0;
|
||||
|
||||
out1:
|
||||
proto_unregister(&l2tp_ip6_prot);
|
||||
out:
|
||||
return err;
|
||||
}
|
||||
|
||||
static void __exit l2tp_ip6_exit(void)
|
||||
{
|
||||
inet6_unregister_protosw(&l2tp_ip6_protosw);
|
||||
inet6_del_protocol(&l2tp_ip6_protocol, IPPROTO_L2TP);
|
||||
proto_unregister(&l2tp_ip6_prot);
|
||||
}
|
||||
|
||||
module_init(l2tp_ip6_init);
|
||||
module_exit(l2tp_ip6_exit);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Chris Elston <celston@katalix.com>");
|
||||
MODULE_DESCRIPTION("L2TP IP encapsulation for IPv6");
|
||||
MODULE_VERSION("1.0");
|
||||
|
||||
/* Use the value of SOCK_DGRAM (2) directory, because __stringify doesn't like
|
||||
* enums
|
||||
*/
|
||||
MODULE_ALIAS_NET_PF_PROTO_TYPE(PF_INET6, 2, IPPROTO_L2TP);
|
911
net/l2tp/l2tp_netlink.c
Normal file
911
net/l2tp/l2tp_netlink.c
Normal file
|
@ -0,0 +1,911 @@
|
|||
/*
|
||||
* L2TP netlink layer, for management
|
||||
*
|
||||
* Copyright (c) 2008,2009,2010 Katalix Systems Ltd
|
||||
*
|
||||
* Partly based on the IrDA nelink implementation
|
||||
* (see net/irda/irnetlink.c) which is:
|
||||
* Copyright (c) 2007 Samuel Ortiz <samuel@sortiz.org>
|
||||
* which is in turn partly based on the wireless netlink code:
|
||||
* Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
|
||||
*
|
||||
* 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 <net/sock.h>
|
||||
#include <net/genetlink.h>
|
||||
#include <net/udp.h>
|
||||
#include <linux/in.h>
|
||||
#include <linux/udp.h>
|
||||
#include <linux/socket.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/list.h>
|
||||
#include <net/net_namespace.h>
|
||||
|
||||
#include <linux/l2tp.h>
|
||||
|
||||
#include "l2tp_core.h"
|
||||
|
||||
|
||||
static struct genl_family l2tp_nl_family = {
|
||||
.id = GENL_ID_GENERATE,
|
||||
.name = L2TP_GENL_NAME,
|
||||
.version = L2TP_GENL_VERSION,
|
||||
.hdrsize = 0,
|
||||
.maxattr = L2TP_ATTR_MAX,
|
||||
.netnsok = true,
|
||||
};
|
||||
|
||||
/* Accessed under genl lock */
|
||||
static const struct l2tp_nl_cmd_ops *l2tp_nl_cmd_ops[__L2TP_PWTYPE_MAX];
|
||||
|
||||
static struct l2tp_session *l2tp_nl_session_find(struct genl_info *info)
|
||||
{
|
||||
u32 tunnel_id;
|
||||
u32 session_id;
|
||||
char *ifname;
|
||||
struct l2tp_tunnel *tunnel;
|
||||
struct l2tp_session *session = NULL;
|
||||
struct net *net = genl_info_net(info);
|
||||
|
||||
if (info->attrs[L2TP_ATTR_IFNAME]) {
|
||||
ifname = nla_data(info->attrs[L2TP_ATTR_IFNAME]);
|
||||
session = l2tp_session_find_by_ifname(net, ifname);
|
||||
} else if ((info->attrs[L2TP_ATTR_SESSION_ID]) &&
|
||||
(info->attrs[L2TP_ATTR_CONN_ID])) {
|
||||
tunnel_id = nla_get_u32(info->attrs[L2TP_ATTR_CONN_ID]);
|
||||
session_id = nla_get_u32(info->attrs[L2TP_ATTR_SESSION_ID]);
|
||||
tunnel = l2tp_tunnel_find(net, tunnel_id);
|
||||
if (tunnel)
|
||||
session = l2tp_session_find(net, tunnel, session_id);
|
||||
}
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
static int l2tp_nl_cmd_noop(struct sk_buff *skb, struct genl_info *info)
|
||||
{
|
||||
struct sk_buff *msg;
|
||||
void *hdr;
|
||||
int ret = -ENOBUFS;
|
||||
|
||||
msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
|
||||
if (!msg) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
hdr = genlmsg_put(msg, info->snd_portid, info->snd_seq,
|
||||
&l2tp_nl_family, 0, L2TP_CMD_NOOP);
|
||||
if (!hdr) {
|
||||
ret = -EMSGSIZE;
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
genlmsg_end(msg, hdr);
|
||||
|
||||
return genlmsg_unicast(genl_info_net(info), msg, info->snd_portid);
|
||||
|
||||
err_out:
|
||||
nlmsg_free(msg);
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int l2tp_nl_cmd_tunnel_create(struct sk_buff *skb, struct genl_info *info)
|
||||
{
|
||||
u32 tunnel_id;
|
||||
u32 peer_tunnel_id;
|
||||
int proto_version;
|
||||
int fd;
|
||||
int ret = 0;
|
||||
struct l2tp_tunnel_cfg cfg = { 0, };
|
||||
struct l2tp_tunnel *tunnel;
|
||||
struct net *net = genl_info_net(info);
|
||||
|
||||
if (!info->attrs[L2TP_ATTR_CONN_ID]) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
tunnel_id = nla_get_u32(info->attrs[L2TP_ATTR_CONN_ID]);
|
||||
|
||||
if (!info->attrs[L2TP_ATTR_PEER_CONN_ID]) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
peer_tunnel_id = nla_get_u32(info->attrs[L2TP_ATTR_PEER_CONN_ID]);
|
||||
|
||||
if (!info->attrs[L2TP_ATTR_PROTO_VERSION]) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
proto_version = nla_get_u8(info->attrs[L2TP_ATTR_PROTO_VERSION]);
|
||||
|
||||
if (!info->attrs[L2TP_ATTR_ENCAP_TYPE]) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
cfg.encap = nla_get_u16(info->attrs[L2TP_ATTR_ENCAP_TYPE]);
|
||||
|
||||
fd = -1;
|
||||
if (info->attrs[L2TP_ATTR_FD]) {
|
||||
fd = nla_get_u32(info->attrs[L2TP_ATTR_FD]);
|
||||
} else {
|
||||
#if IS_ENABLED(CONFIG_IPV6)
|
||||
if (info->attrs[L2TP_ATTR_IP6_SADDR] &&
|
||||
info->attrs[L2TP_ATTR_IP6_DADDR]) {
|
||||
cfg.local_ip6 = nla_data(
|
||||
info->attrs[L2TP_ATTR_IP6_SADDR]);
|
||||
cfg.peer_ip6 = nla_data(
|
||||
info->attrs[L2TP_ATTR_IP6_DADDR]);
|
||||
} else
|
||||
#endif
|
||||
if (info->attrs[L2TP_ATTR_IP_SADDR] &&
|
||||
info->attrs[L2TP_ATTR_IP_DADDR]) {
|
||||
cfg.local_ip.s_addr = nla_get_be32(
|
||||
info->attrs[L2TP_ATTR_IP_SADDR]);
|
||||
cfg.peer_ip.s_addr = nla_get_be32(
|
||||
info->attrs[L2TP_ATTR_IP_DADDR]);
|
||||
} else {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
if (info->attrs[L2TP_ATTR_UDP_SPORT])
|
||||
cfg.local_udp_port = nla_get_u16(info->attrs[L2TP_ATTR_UDP_SPORT]);
|
||||
if (info->attrs[L2TP_ATTR_UDP_DPORT])
|
||||
cfg.peer_udp_port = nla_get_u16(info->attrs[L2TP_ATTR_UDP_DPORT]);
|
||||
if (info->attrs[L2TP_ATTR_UDP_CSUM])
|
||||
cfg.use_udp_checksums = nla_get_flag(info->attrs[L2TP_ATTR_UDP_CSUM]);
|
||||
}
|
||||
|
||||
if (info->attrs[L2TP_ATTR_DEBUG])
|
||||
cfg.debug = nla_get_u32(info->attrs[L2TP_ATTR_DEBUG]);
|
||||
|
||||
tunnel = l2tp_tunnel_find(net, tunnel_id);
|
||||
if (tunnel != NULL) {
|
||||
ret = -EEXIST;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = -EINVAL;
|
||||
switch (cfg.encap) {
|
||||
case L2TP_ENCAPTYPE_UDP:
|
||||
case L2TP_ENCAPTYPE_IP:
|
||||
ret = l2tp_tunnel_create(net, fd, proto_version, tunnel_id,
|
||||
peer_tunnel_id, &cfg, &tunnel);
|
||||
break;
|
||||
}
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int l2tp_nl_cmd_tunnel_delete(struct sk_buff *skb, struct genl_info *info)
|
||||
{
|
||||
struct l2tp_tunnel *tunnel;
|
||||
u32 tunnel_id;
|
||||
int ret = 0;
|
||||
struct net *net = genl_info_net(info);
|
||||
|
||||
if (!info->attrs[L2TP_ATTR_CONN_ID]) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
tunnel_id = nla_get_u32(info->attrs[L2TP_ATTR_CONN_ID]);
|
||||
|
||||
tunnel = l2tp_tunnel_find(net, tunnel_id);
|
||||
if (tunnel == NULL) {
|
||||
ret = -ENODEV;
|
||||
goto out;
|
||||
}
|
||||
|
||||
(void) l2tp_tunnel_delete(tunnel);
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int l2tp_nl_cmd_tunnel_modify(struct sk_buff *skb, struct genl_info *info)
|
||||
{
|
||||
struct l2tp_tunnel *tunnel;
|
||||
u32 tunnel_id;
|
||||
int ret = 0;
|
||||
struct net *net = genl_info_net(info);
|
||||
|
||||
if (!info->attrs[L2TP_ATTR_CONN_ID]) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
tunnel_id = nla_get_u32(info->attrs[L2TP_ATTR_CONN_ID]);
|
||||
|
||||
tunnel = l2tp_tunnel_find(net, tunnel_id);
|
||||
if (tunnel == NULL) {
|
||||
ret = -ENODEV;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (info->attrs[L2TP_ATTR_DEBUG])
|
||||
tunnel->debug = nla_get_u32(info->attrs[L2TP_ATTR_DEBUG]);
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int l2tp_nl_tunnel_send(struct sk_buff *skb, u32 portid, u32 seq, int flags,
|
||||
struct l2tp_tunnel *tunnel)
|
||||
{
|
||||
void *hdr;
|
||||
struct nlattr *nest;
|
||||
struct sock *sk = NULL;
|
||||
struct inet_sock *inet;
|
||||
#if IS_ENABLED(CONFIG_IPV6)
|
||||
struct ipv6_pinfo *np = NULL;
|
||||
#endif
|
||||
|
||||
hdr = genlmsg_put(skb, portid, seq, &l2tp_nl_family, flags,
|
||||
L2TP_CMD_TUNNEL_GET);
|
||||
if (!hdr)
|
||||
return -EMSGSIZE;
|
||||
|
||||
if (nla_put_u8(skb, L2TP_ATTR_PROTO_VERSION, tunnel->version) ||
|
||||
nla_put_u32(skb, L2TP_ATTR_CONN_ID, tunnel->tunnel_id) ||
|
||||
nla_put_u32(skb, L2TP_ATTR_PEER_CONN_ID, tunnel->peer_tunnel_id) ||
|
||||
nla_put_u32(skb, L2TP_ATTR_DEBUG, tunnel->debug) ||
|
||||
nla_put_u16(skb, L2TP_ATTR_ENCAP_TYPE, tunnel->encap))
|
||||
goto nla_put_failure;
|
||||
|
||||
nest = nla_nest_start(skb, L2TP_ATTR_STATS);
|
||||
if (nest == NULL)
|
||||
goto nla_put_failure;
|
||||
|
||||
if (nla_put_u64(skb, L2TP_ATTR_TX_PACKETS,
|
||||
atomic_long_read(&tunnel->stats.tx_packets)) ||
|
||||
nla_put_u64(skb, L2TP_ATTR_TX_BYTES,
|
||||
atomic_long_read(&tunnel->stats.tx_bytes)) ||
|
||||
nla_put_u64(skb, L2TP_ATTR_TX_ERRORS,
|
||||
atomic_long_read(&tunnel->stats.tx_errors)) ||
|
||||
nla_put_u64(skb, L2TP_ATTR_RX_PACKETS,
|
||||
atomic_long_read(&tunnel->stats.rx_packets)) ||
|
||||
nla_put_u64(skb, L2TP_ATTR_RX_BYTES,
|
||||
atomic_long_read(&tunnel->stats.rx_bytes)) ||
|
||||
nla_put_u64(skb, L2TP_ATTR_RX_SEQ_DISCARDS,
|
||||
atomic_long_read(&tunnel->stats.rx_seq_discards)) ||
|
||||
nla_put_u64(skb, L2TP_ATTR_RX_OOS_PACKETS,
|
||||
atomic_long_read(&tunnel->stats.rx_oos_packets)) ||
|
||||
nla_put_u64(skb, L2TP_ATTR_RX_ERRORS,
|
||||
atomic_long_read(&tunnel->stats.rx_errors)))
|
||||
goto nla_put_failure;
|
||||
nla_nest_end(skb, nest);
|
||||
|
||||
sk = tunnel->sock;
|
||||
if (!sk)
|
||||
goto out;
|
||||
|
||||
#if IS_ENABLED(CONFIG_IPV6)
|
||||
if (sk->sk_family == AF_INET6)
|
||||
np = inet6_sk(sk);
|
||||
#endif
|
||||
|
||||
inet = inet_sk(sk);
|
||||
|
||||
switch (tunnel->encap) {
|
||||
case L2TP_ENCAPTYPE_UDP:
|
||||
if (nla_put_u16(skb, L2TP_ATTR_UDP_SPORT, ntohs(inet->inet_sport)) ||
|
||||
nla_put_u16(skb, L2TP_ATTR_UDP_DPORT, ntohs(inet->inet_dport)) ||
|
||||
nla_put_u8(skb, L2TP_ATTR_UDP_CSUM,
|
||||
(sk->sk_no_check != UDP_CSUM_NOXMIT)))
|
||||
goto nla_put_failure;
|
||||
/* NOBREAK */
|
||||
case L2TP_ENCAPTYPE_IP:
|
||||
#if IS_ENABLED(CONFIG_IPV6)
|
||||
if (np) {
|
||||
if (nla_put(skb, L2TP_ATTR_IP6_SADDR, sizeof(np->saddr),
|
||||
&np->saddr) ||
|
||||
nla_put(skb, L2TP_ATTR_IP6_DADDR, sizeof(np->daddr),
|
||||
&np->daddr))
|
||||
goto nla_put_failure;
|
||||
} else
|
||||
#endif
|
||||
if (nla_put_be32(skb, L2TP_ATTR_IP_SADDR, inet->inet_saddr) ||
|
||||
nla_put_be32(skb, L2TP_ATTR_IP_DADDR, inet->inet_daddr))
|
||||
goto nla_put_failure;
|
||||
break;
|
||||
}
|
||||
|
||||
out:
|
||||
return genlmsg_end(skb, hdr);
|
||||
|
||||
nla_put_failure:
|
||||
genlmsg_cancel(skb, hdr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int l2tp_nl_cmd_tunnel_get(struct sk_buff *skb, struct genl_info *info)
|
||||
{
|
||||
struct l2tp_tunnel *tunnel;
|
||||
struct sk_buff *msg;
|
||||
u32 tunnel_id;
|
||||
int ret = -ENOBUFS;
|
||||
struct net *net = genl_info_net(info);
|
||||
|
||||
if (!info->attrs[L2TP_ATTR_CONN_ID]) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
tunnel_id = nla_get_u32(info->attrs[L2TP_ATTR_CONN_ID]);
|
||||
|
||||
tunnel = l2tp_tunnel_find(net, tunnel_id);
|
||||
if (tunnel == NULL) {
|
||||
ret = -ENODEV;
|
||||
goto out;
|
||||
}
|
||||
|
||||
msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
|
||||
if (!msg) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = l2tp_nl_tunnel_send(msg, info->snd_portid, info->snd_seq,
|
||||
NLM_F_ACK, tunnel);
|
||||
if (ret < 0)
|
||||
goto err_out;
|
||||
|
||||
return genlmsg_unicast(net, msg, info->snd_portid);
|
||||
|
||||
err_out:
|
||||
nlmsg_free(msg);
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int l2tp_nl_cmd_tunnel_dump(struct sk_buff *skb, struct netlink_callback *cb)
|
||||
{
|
||||
int ti = cb->args[0];
|
||||
struct l2tp_tunnel *tunnel;
|
||||
struct net *net = sock_net(skb->sk);
|
||||
|
||||
for (;;) {
|
||||
tunnel = l2tp_tunnel_find_nth(net, ti);
|
||||
if (tunnel == NULL)
|
||||
goto out;
|
||||
|
||||
if (l2tp_nl_tunnel_send(skb, NETLINK_CB(cb->skb).portid,
|
||||
cb->nlh->nlmsg_seq, NLM_F_MULTI,
|
||||
tunnel) <= 0)
|
||||
goto out;
|
||||
|
||||
ti++;
|
||||
}
|
||||
|
||||
out:
|
||||
cb->args[0] = ti;
|
||||
|
||||
return skb->len;
|
||||
}
|
||||
|
||||
static int l2tp_nl_cmd_session_create(struct sk_buff *skb, struct genl_info *info)
|
||||
{
|
||||
u32 tunnel_id = 0;
|
||||
u32 session_id;
|
||||
u32 peer_session_id;
|
||||
int ret = 0;
|
||||
struct l2tp_tunnel *tunnel;
|
||||
struct l2tp_session *session;
|
||||
struct l2tp_session_cfg cfg = { 0, };
|
||||
struct net *net = genl_info_net(info);
|
||||
|
||||
if (!info->attrs[L2TP_ATTR_CONN_ID]) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
tunnel_id = nla_get_u32(info->attrs[L2TP_ATTR_CONN_ID]);
|
||||
tunnel = l2tp_tunnel_find(net, tunnel_id);
|
||||
if (!tunnel) {
|
||||
ret = -ENODEV;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!info->attrs[L2TP_ATTR_SESSION_ID]) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
session_id = nla_get_u32(info->attrs[L2TP_ATTR_SESSION_ID]);
|
||||
session = l2tp_session_find(net, tunnel, session_id);
|
||||
if (session) {
|
||||
ret = -EEXIST;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!info->attrs[L2TP_ATTR_PEER_SESSION_ID]) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
peer_session_id = nla_get_u32(info->attrs[L2TP_ATTR_PEER_SESSION_ID]);
|
||||
|
||||
if (!info->attrs[L2TP_ATTR_PW_TYPE]) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
cfg.pw_type = nla_get_u16(info->attrs[L2TP_ATTR_PW_TYPE]);
|
||||
if (cfg.pw_type >= __L2TP_PWTYPE_MAX) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (tunnel->version > 2) {
|
||||
if (info->attrs[L2TP_ATTR_OFFSET])
|
||||
cfg.offset = nla_get_u16(info->attrs[L2TP_ATTR_OFFSET]);
|
||||
|
||||
if (info->attrs[L2TP_ATTR_DATA_SEQ])
|
||||
cfg.data_seq = nla_get_u8(info->attrs[L2TP_ATTR_DATA_SEQ]);
|
||||
|
||||
cfg.l2specific_type = L2TP_L2SPECTYPE_DEFAULT;
|
||||
if (info->attrs[L2TP_ATTR_L2SPEC_TYPE])
|
||||
cfg.l2specific_type = nla_get_u8(info->attrs[L2TP_ATTR_L2SPEC_TYPE]);
|
||||
|
||||
cfg.l2specific_len = 4;
|
||||
if (info->attrs[L2TP_ATTR_L2SPEC_LEN])
|
||||
cfg.l2specific_len = nla_get_u8(info->attrs[L2TP_ATTR_L2SPEC_LEN]);
|
||||
|
||||
if (info->attrs[L2TP_ATTR_COOKIE]) {
|
||||
u16 len = nla_len(info->attrs[L2TP_ATTR_COOKIE]);
|
||||
if (len > 8) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
cfg.cookie_len = len;
|
||||
memcpy(&cfg.cookie[0], nla_data(info->attrs[L2TP_ATTR_COOKIE]), len);
|
||||
}
|
||||
if (info->attrs[L2TP_ATTR_PEER_COOKIE]) {
|
||||
u16 len = nla_len(info->attrs[L2TP_ATTR_PEER_COOKIE]);
|
||||
if (len > 8) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
cfg.peer_cookie_len = len;
|
||||
memcpy(&cfg.peer_cookie[0], nla_data(info->attrs[L2TP_ATTR_PEER_COOKIE]), len);
|
||||
}
|
||||
if (info->attrs[L2TP_ATTR_IFNAME])
|
||||
cfg.ifname = nla_data(info->attrs[L2TP_ATTR_IFNAME]);
|
||||
|
||||
if (info->attrs[L2TP_ATTR_VLAN_ID])
|
||||
cfg.vlan_id = nla_get_u16(info->attrs[L2TP_ATTR_VLAN_ID]);
|
||||
}
|
||||
|
||||
if (info->attrs[L2TP_ATTR_DEBUG])
|
||||
cfg.debug = nla_get_u32(info->attrs[L2TP_ATTR_DEBUG]);
|
||||
|
||||
if (info->attrs[L2TP_ATTR_RECV_SEQ])
|
||||
cfg.recv_seq = nla_get_u8(info->attrs[L2TP_ATTR_RECV_SEQ]);
|
||||
|
||||
if (info->attrs[L2TP_ATTR_SEND_SEQ])
|
||||
cfg.send_seq = nla_get_u8(info->attrs[L2TP_ATTR_SEND_SEQ]);
|
||||
|
||||
if (info->attrs[L2TP_ATTR_LNS_MODE])
|
||||
cfg.lns_mode = nla_get_u8(info->attrs[L2TP_ATTR_LNS_MODE]);
|
||||
|
||||
if (info->attrs[L2TP_ATTR_RECV_TIMEOUT])
|
||||
cfg.reorder_timeout = nla_get_msecs(info->attrs[L2TP_ATTR_RECV_TIMEOUT]);
|
||||
|
||||
if (info->attrs[L2TP_ATTR_MTU])
|
||||
cfg.mtu = nla_get_u16(info->attrs[L2TP_ATTR_MTU]);
|
||||
|
||||
if (info->attrs[L2TP_ATTR_MRU])
|
||||
cfg.mru = nla_get_u16(info->attrs[L2TP_ATTR_MRU]);
|
||||
|
||||
if ((l2tp_nl_cmd_ops[cfg.pw_type] == NULL) ||
|
||||
(l2tp_nl_cmd_ops[cfg.pw_type]->session_create == NULL)) {
|
||||
ret = -EPROTONOSUPPORT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Check that pseudowire-specific params are present */
|
||||
switch (cfg.pw_type) {
|
||||
case L2TP_PWTYPE_NONE:
|
||||
break;
|
||||
case L2TP_PWTYPE_ETH_VLAN:
|
||||
if (!info->attrs[L2TP_ATTR_VLAN_ID]) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
break;
|
||||
case L2TP_PWTYPE_ETH:
|
||||
break;
|
||||
case L2TP_PWTYPE_PPP:
|
||||
case L2TP_PWTYPE_PPP_AC:
|
||||
break;
|
||||
case L2TP_PWTYPE_IP:
|
||||
default:
|
||||
ret = -EPROTONOSUPPORT;
|
||||
break;
|
||||
}
|
||||
|
||||
ret = -EPROTONOSUPPORT;
|
||||
if (l2tp_nl_cmd_ops[cfg.pw_type]->session_create)
|
||||
ret = (*l2tp_nl_cmd_ops[cfg.pw_type]->session_create)(net, tunnel_id,
|
||||
session_id, peer_session_id, &cfg);
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int l2tp_nl_cmd_session_delete(struct sk_buff *skb, struct genl_info *info)
|
||||
{
|
||||
int ret = 0;
|
||||
struct l2tp_session *session;
|
||||
u16 pw_type;
|
||||
|
||||
session = l2tp_nl_session_find(info);
|
||||
if (session == NULL) {
|
||||
ret = -ENODEV;
|
||||
goto out;
|
||||
}
|
||||
|
||||
pw_type = session->pwtype;
|
||||
if (pw_type < __L2TP_PWTYPE_MAX)
|
||||
if (l2tp_nl_cmd_ops[pw_type] && l2tp_nl_cmd_ops[pw_type]->session_delete)
|
||||
ret = (*l2tp_nl_cmd_ops[pw_type]->session_delete)(session);
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int l2tp_nl_cmd_session_modify(struct sk_buff *skb, struct genl_info *info)
|
||||
{
|
||||
int ret = 0;
|
||||
struct l2tp_session *session;
|
||||
|
||||
session = l2tp_nl_session_find(info);
|
||||
if (session == NULL) {
|
||||
ret = -ENODEV;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (info->attrs[L2TP_ATTR_DEBUG])
|
||||
session->debug = nla_get_u32(info->attrs[L2TP_ATTR_DEBUG]);
|
||||
|
||||
if (info->attrs[L2TP_ATTR_DATA_SEQ])
|
||||
session->data_seq = nla_get_u8(info->attrs[L2TP_ATTR_DATA_SEQ]);
|
||||
|
||||
if (info->attrs[L2TP_ATTR_RECV_SEQ])
|
||||
session->recv_seq = nla_get_u8(info->attrs[L2TP_ATTR_RECV_SEQ]);
|
||||
|
||||
if (info->attrs[L2TP_ATTR_SEND_SEQ])
|
||||
session->send_seq = nla_get_u8(info->attrs[L2TP_ATTR_SEND_SEQ]);
|
||||
|
||||
if (info->attrs[L2TP_ATTR_LNS_MODE])
|
||||
session->lns_mode = nla_get_u8(info->attrs[L2TP_ATTR_LNS_MODE]);
|
||||
|
||||
if (info->attrs[L2TP_ATTR_RECV_TIMEOUT])
|
||||
session->reorder_timeout = nla_get_msecs(info->attrs[L2TP_ATTR_RECV_TIMEOUT]);
|
||||
|
||||
if (info->attrs[L2TP_ATTR_MTU])
|
||||
session->mtu = nla_get_u16(info->attrs[L2TP_ATTR_MTU]);
|
||||
|
||||
if (info->attrs[L2TP_ATTR_MRU])
|
||||
session->mru = nla_get_u16(info->attrs[L2TP_ATTR_MRU]);
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int l2tp_nl_session_send(struct sk_buff *skb, u32 portid, u32 seq, int flags,
|
||||
struct l2tp_session *session)
|
||||
{
|
||||
void *hdr;
|
||||
struct nlattr *nest;
|
||||
struct l2tp_tunnel *tunnel = session->tunnel;
|
||||
struct sock *sk = NULL;
|
||||
|
||||
sk = tunnel->sock;
|
||||
|
||||
hdr = genlmsg_put(skb, portid, seq, &l2tp_nl_family, flags, L2TP_CMD_SESSION_GET);
|
||||
if (!hdr)
|
||||
return -EMSGSIZE;
|
||||
|
||||
if (nla_put_u32(skb, L2TP_ATTR_CONN_ID, tunnel->tunnel_id) ||
|
||||
nla_put_u32(skb, L2TP_ATTR_SESSION_ID, session->session_id) ||
|
||||
nla_put_u32(skb, L2TP_ATTR_PEER_CONN_ID, tunnel->peer_tunnel_id) ||
|
||||
nla_put_u32(skb, L2TP_ATTR_PEER_SESSION_ID,
|
||||
session->peer_session_id) ||
|
||||
nla_put_u32(skb, L2TP_ATTR_DEBUG, session->debug) ||
|
||||
nla_put_u16(skb, L2TP_ATTR_PW_TYPE, session->pwtype) ||
|
||||
nla_put_u16(skb, L2TP_ATTR_MTU, session->mtu) ||
|
||||
(session->mru &&
|
||||
nla_put_u16(skb, L2TP_ATTR_MRU, session->mru)))
|
||||
goto nla_put_failure;
|
||||
|
||||
if ((session->ifname[0] &&
|
||||
nla_put_string(skb, L2TP_ATTR_IFNAME, session->ifname)) ||
|
||||
(session->cookie_len &&
|
||||
nla_put(skb, L2TP_ATTR_COOKIE, session->cookie_len,
|
||||
&session->cookie[0])) ||
|
||||
(session->peer_cookie_len &&
|
||||
nla_put(skb, L2TP_ATTR_PEER_COOKIE, session->peer_cookie_len,
|
||||
&session->peer_cookie[0])) ||
|
||||
nla_put_u8(skb, L2TP_ATTR_RECV_SEQ, session->recv_seq) ||
|
||||
nla_put_u8(skb, L2TP_ATTR_SEND_SEQ, session->send_seq) ||
|
||||
nla_put_u8(skb, L2TP_ATTR_LNS_MODE, session->lns_mode) ||
|
||||
#ifdef CONFIG_XFRM
|
||||
(((sk) && (sk->sk_policy[0] || sk->sk_policy[1])) &&
|
||||
nla_put_u8(skb, L2TP_ATTR_USING_IPSEC, 1)) ||
|
||||
#endif
|
||||
(session->reorder_timeout &&
|
||||
nla_put_msecs(skb, L2TP_ATTR_RECV_TIMEOUT, session->reorder_timeout)))
|
||||
goto nla_put_failure;
|
||||
|
||||
nest = nla_nest_start(skb, L2TP_ATTR_STATS);
|
||||
if (nest == NULL)
|
||||
goto nla_put_failure;
|
||||
|
||||
if (nla_put_u64(skb, L2TP_ATTR_TX_PACKETS,
|
||||
atomic_long_read(&session->stats.tx_packets)) ||
|
||||
nla_put_u64(skb, L2TP_ATTR_TX_BYTES,
|
||||
atomic_long_read(&session->stats.tx_bytes)) ||
|
||||
nla_put_u64(skb, L2TP_ATTR_TX_ERRORS,
|
||||
atomic_long_read(&session->stats.tx_errors)) ||
|
||||
nla_put_u64(skb, L2TP_ATTR_RX_PACKETS,
|
||||
atomic_long_read(&session->stats.rx_packets)) ||
|
||||
nla_put_u64(skb, L2TP_ATTR_RX_BYTES,
|
||||
atomic_long_read(&session->stats.rx_bytes)) ||
|
||||
nla_put_u64(skb, L2TP_ATTR_RX_SEQ_DISCARDS,
|
||||
atomic_long_read(&session->stats.rx_seq_discards)) ||
|
||||
nla_put_u64(skb, L2TP_ATTR_RX_OOS_PACKETS,
|
||||
atomic_long_read(&session->stats.rx_oos_packets)) ||
|
||||
nla_put_u64(skb, L2TP_ATTR_RX_ERRORS,
|
||||
atomic_long_read(&session->stats.rx_errors)))
|
||||
goto nla_put_failure;
|
||||
nla_nest_end(skb, nest);
|
||||
|
||||
return genlmsg_end(skb, hdr);
|
||||
|
||||
nla_put_failure:
|
||||
genlmsg_cancel(skb, hdr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int l2tp_nl_cmd_session_get(struct sk_buff *skb, struct genl_info *info)
|
||||
{
|
||||
struct l2tp_session *session;
|
||||
struct sk_buff *msg;
|
||||
int ret;
|
||||
|
||||
session = l2tp_nl_session_find(info);
|
||||
if (session == NULL) {
|
||||
ret = -ENODEV;
|
||||
goto out;
|
||||
}
|
||||
|
||||
msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
|
||||
if (!msg) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = l2tp_nl_session_send(msg, info->snd_portid, info->snd_seq,
|
||||
0, session);
|
||||
if (ret < 0)
|
||||
goto err_out;
|
||||
|
||||
return genlmsg_unicast(genl_info_net(info), msg, info->snd_portid);
|
||||
|
||||
err_out:
|
||||
nlmsg_free(msg);
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int l2tp_nl_cmd_session_dump(struct sk_buff *skb, struct netlink_callback *cb)
|
||||
{
|
||||
struct net *net = sock_net(skb->sk);
|
||||
struct l2tp_session *session;
|
||||
struct l2tp_tunnel *tunnel = NULL;
|
||||
int ti = cb->args[0];
|
||||
int si = cb->args[1];
|
||||
|
||||
for (;;) {
|
||||
if (tunnel == NULL) {
|
||||
tunnel = l2tp_tunnel_find_nth(net, ti);
|
||||
if (tunnel == NULL)
|
||||
goto out;
|
||||
}
|
||||
|
||||
session = l2tp_session_find_nth(tunnel, si);
|
||||
if (session == NULL) {
|
||||
ti++;
|
||||
tunnel = NULL;
|
||||
si = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (l2tp_nl_session_send(skb, NETLINK_CB(cb->skb).portid,
|
||||
cb->nlh->nlmsg_seq, NLM_F_MULTI,
|
||||
session) <= 0)
|
||||
break;
|
||||
|
||||
si++;
|
||||
}
|
||||
|
||||
out:
|
||||
cb->args[0] = ti;
|
||||
cb->args[1] = si;
|
||||
|
||||
return skb->len;
|
||||
}
|
||||
|
||||
static struct nla_policy l2tp_nl_policy[L2TP_ATTR_MAX + 1] = {
|
||||
[L2TP_ATTR_NONE] = { .type = NLA_UNSPEC, },
|
||||
[L2TP_ATTR_PW_TYPE] = { .type = NLA_U16, },
|
||||
[L2TP_ATTR_ENCAP_TYPE] = { .type = NLA_U16, },
|
||||
[L2TP_ATTR_OFFSET] = { .type = NLA_U16, },
|
||||
[L2TP_ATTR_DATA_SEQ] = { .type = NLA_U8, },
|
||||
[L2TP_ATTR_L2SPEC_TYPE] = { .type = NLA_U8, },
|
||||
[L2TP_ATTR_L2SPEC_LEN] = { .type = NLA_U8, },
|
||||
[L2TP_ATTR_PROTO_VERSION] = { .type = NLA_U8, },
|
||||
[L2TP_ATTR_CONN_ID] = { .type = NLA_U32, },
|
||||
[L2TP_ATTR_PEER_CONN_ID] = { .type = NLA_U32, },
|
||||
[L2TP_ATTR_SESSION_ID] = { .type = NLA_U32, },
|
||||
[L2TP_ATTR_PEER_SESSION_ID] = { .type = NLA_U32, },
|
||||
[L2TP_ATTR_UDP_CSUM] = { .type = NLA_U8, },
|
||||
[L2TP_ATTR_VLAN_ID] = { .type = NLA_U16, },
|
||||
[L2TP_ATTR_DEBUG] = { .type = NLA_U32, },
|
||||
[L2TP_ATTR_RECV_SEQ] = { .type = NLA_U8, },
|
||||
[L2TP_ATTR_SEND_SEQ] = { .type = NLA_U8, },
|
||||
[L2TP_ATTR_LNS_MODE] = { .type = NLA_U8, },
|
||||
[L2TP_ATTR_USING_IPSEC] = { .type = NLA_U8, },
|
||||
[L2TP_ATTR_RECV_TIMEOUT] = { .type = NLA_MSECS, },
|
||||
[L2TP_ATTR_FD] = { .type = NLA_U32, },
|
||||
[L2TP_ATTR_IP_SADDR] = { .type = NLA_U32, },
|
||||
[L2TP_ATTR_IP_DADDR] = { .type = NLA_U32, },
|
||||
[L2TP_ATTR_UDP_SPORT] = { .type = NLA_U16, },
|
||||
[L2TP_ATTR_UDP_DPORT] = { .type = NLA_U16, },
|
||||
[L2TP_ATTR_MTU] = { .type = NLA_U16, },
|
||||
[L2TP_ATTR_MRU] = { .type = NLA_U16, },
|
||||
[L2TP_ATTR_STATS] = { .type = NLA_NESTED, },
|
||||
[L2TP_ATTR_IP6_SADDR] = {
|
||||
.type = NLA_BINARY,
|
||||
.len = sizeof(struct in6_addr),
|
||||
},
|
||||
[L2TP_ATTR_IP6_DADDR] = {
|
||||
.type = NLA_BINARY,
|
||||
.len = sizeof(struct in6_addr),
|
||||
},
|
||||
[L2TP_ATTR_IFNAME] = {
|
||||
.type = NLA_NUL_STRING,
|
||||
.len = IFNAMSIZ - 1,
|
||||
},
|
||||
[L2TP_ATTR_COOKIE] = {
|
||||
.type = NLA_BINARY,
|
||||
.len = 8,
|
||||
},
|
||||
[L2TP_ATTR_PEER_COOKIE] = {
|
||||
.type = NLA_BINARY,
|
||||
.len = 8,
|
||||
},
|
||||
};
|
||||
|
||||
static struct genl_ops l2tp_nl_ops[] = {
|
||||
{
|
||||
.cmd = L2TP_CMD_NOOP,
|
||||
.doit = l2tp_nl_cmd_noop,
|
||||
.policy = l2tp_nl_policy,
|
||||
/* can be retrieved by unprivileged users */
|
||||
},
|
||||
{
|
||||
.cmd = L2TP_CMD_TUNNEL_CREATE,
|
||||
.doit = l2tp_nl_cmd_tunnel_create,
|
||||
.policy = l2tp_nl_policy,
|
||||
.flags = GENL_ADMIN_PERM,
|
||||
},
|
||||
{
|
||||
.cmd = L2TP_CMD_TUNNEL_DELETE,
|
||||
.doit = l2tp_nl_cmd_tunnel_delete,
|
||||
.policy = l2tp_nl_policy,
|
||||
.flags = GENL_ADMIN_PERM,
|
||||
},
|
||||
{
|
||||
.cmd = L2TP_CMD_TUNNEL_MODIFY,
|
||||
.doit = l2tp_nl_cmd_tunnel_modify,
|
||||
.policy = l2tp_nl_policy,
|
||||
.flags = GENL_ADMIN_PERM,
|
||||
},
|
||||
{
|
||||
.cmd = L2TP_CMD_TUNNEL_GET,
|
||||
.doit = l2tp_nl_cmd_tunnel_get,
|
||||
.dumpit = l2tp_nl_cmd_tunnel_dump,
|
||||
.policy = l2tp_nl_policy,
|
||||
.flags = GENL_ADMIN_PERM,
|
||||
},
|
||||
{
|
||||
.cmd = L2TP_CMD_SESSION_CREATE,
|
||||
.doit = l2tp_nl_cmd_session_create,
|
||||
.policy = l2tp_nl_policy,
|
||||
.flags = GENL_ADMIN_PERM,
|
||||
},
|
||||
{
|
||||
.cmd = L2TP_CMD_SESSION_DELETE,
|
||||
.doit = l2tp_nl_cmd_session_delete,
|
||||
.policy = l2tp_nl_policy,
|
||||
.flags = GENL_ADMIN_PERM,
|
||||
},
|
||||
{
|
||||
.cmd = L2TP_CMD_SESSION_MODIFY,
|
||||
.doit = l2tp_nl_cmd_session_modify,
|
||||
.policy = l2tp_nl_policy,
|
||||
.flags = GENL_ADMIN_PERM,
|
||||
},
|
||||
{
|
||||
.cmd = L2TP_CMD_SESSION_GET,
|
||||
.doit = l2tp_nl_cmd_session_get,
|
||||
.dumpit = l2tp_nl_cmd_session_dump,
|
||||
.policy = l2tp_nl_policy,
|
||||
.flags = GENL_ADMIN_PERM,
|
||||
},
|
||||
};
|
||||
|
||||
int l2tp_nl_register_ops(enum l2tp_pwtype pw_type, const struct l2tp_nl_cmd_ops *ops)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = -EINVAL;
|
||||
if (pw_type >= __L2TP_PWTYPE_MAX)
|
||||
goto err;
|
||||
|
||||
genl_lock();
|
||||
ret = -EBUSY;
|
||||
if (l2tp_nl_cmd_ops[pw_type])
|
||||
goto out;
|
||||
|
||||
l2tp_nl_cmd_ops[pw_type] = ops;
|
||||
ret = 0;
|
||||
|
||||
out:
|
||||
genl_unlock();
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(l2tp_nl_register_ops);
|
||||
|
||||
void l2tp_nl_unregister_ops(enum l2tp_pwtype pw_type)
|
||||
{
|
||||
if (pw_type < __L2TP_PWTYPE_MAX) {
|
||||
genl_lock();
|
||||
l2tp_nl_cmd_ops[pw_type] = NULL;
|
||||
genl_unlock();
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(l2tp_nl_unregister_ops);
|
||||
|
||||
static int l2tp_nl_init(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
pr_info("L2TP netlink interface\n");
|
||||
err = genl_register_family_with_ops(&l2tp_nl_family, l2tp_nl_ops,
|
||||
ARRAY_SIZE(l2tp_nl_ops));
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static void l2tp_nl_cleanup(void)
|
||||
{
|
||||
genl_unregister_family(&l2tp_nl_family);
|
||||
}
|
||||
|
||||
module_init(l2tp_nl_init);
|
||||
module_exit(l2tp_nl_cleanup);
|
||||
|
||||
MODULE_AUTHOR("James Chapman <jchapman@katalix.com>");
|
||||
MODULE_DESCRIPTION("L2TP netlink");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_VERSION("1.0");
|
||||
MODULE_ALIAS_GENL_FAMILY("l2tp");
|
1867
net/l2tp/l2tp_ppp.c
Normal file
1867
net/l2tp/l2tp_ppp.c
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue