l2tp: Add L2TPv3 IP encapsulation (no UDP) support
This patch adds a new L2TPIP socket family and modifies the core to handle the case where there is no UDP header in the L2TP packet. L2TP/IP uses IP protocol 115. Since L2TP/UDP and L2TP/IP packets differ in layout, the datapath packet handling code needs changes too. Userspace uses an L2TPIP socket instead of a UDP socket when IP encapsulation is required. We can't use raw sockets for this because the semantics of raw sockets don't lend themselves to the socket-per-tunnel model - we need to Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
parent
e0d4435f93
commit
0d76751fad
7 changed files with 850 additions and 62 deletions
38
include/linux/l2tp.h
Normal file
38
include/linux/l2tp.h
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* L2TP-over-IP socket for L2TPv3.
|
||||||
|
*
|
||||||
|
* Author: James Chapman <jchapman@katalix.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _LINUX_L2TP_H_
|
||||||
|
#define _LINUX_L2TP_H_
|
||||||
|
|
||||||
|
#include <linux/types.h>
|
||||||
|
#ifdef __KERNEL__
|
||||||
|
#include <linux/socket.h>
|
||||||
|
#include <linux/in.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define IPPROTO_L2TP 115
|
||||||
|
|
||||||
|
/**
|
||||||
|
* struct sockaddr_l2tpip - the sockaddr structure for L2TP-over-IP sockets
|
||||||
|
* @l2tp_family: address family number AF_L2TPIP.
|
||||||
|
* @l2tp_addr: protocol specific address information
|
||||||
|
* @l2tp_conn_id: connection id of tunnel
|
||||||
|
*/
|
||||||
|
struct sockaddr_l2tpip {
|
||||||
|
/* The first fields must match struct sockaddr_in */
|
||||||
|
sa_family_t l2tp_family; /* AF_INET */
|
||||||
|
__be16 l2tp_unused; /* INET port number (unused) */
|
||||||
|
struct in_addr l2tp_addr; /* Internet address */
|
||||||
|
|
||||||
|
__u32 l2tp_conn_id; /* Connection ID of tunnel */
|
||||||
|
|
||||||
|
/* Pad to size of `struct sockaddr'. */
|
||||||
|
unsigned char __pad[sizeof(struct sockaddr) - sizeof(sa_family_t) -
|
||||||
|
sizeof(__be16) - sizeof(struct in_addr) -
|
||||||
|
sizeof(__u32)];
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -51,3 +51,20 @@ config L2TP_V3
|
||||||
If you are connecting to L2TPv3 equipment, or you want to
|
If you are connecting to L2TPv3 equipment, or you want to
|
||||||
tunnel raw ethernet frames using L2TP, say Y here. If
|
tunnel raw ethernet frames using L2TP, say Y here. If
|
||||||
unsure, say N.
|
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.
|
||||||
|
|
|
@ -6,3 +6,4 @@ obj-$(CONFIG_L2TP) += l2tp_core.o
|
||||||
|
|
||||||
# Build l2tp as modules if L2TP is M
|
# Build l2tp as modules if L2TP is M
|
||||||
obj-$(subst y,$(CONFIG_L2TP),$(CONFIG_PPPOL2TP)) += l2tp_ppp.o
|
obj-$(subst y,$(CONFIG_L2TP),$(CONFIG_PPPOL2TP)) += l2tp_ppp.o
|
||||||
|
obj-$(subst y,$(CONFIG_L2TP),$(CONFIG_L2TP_IP)) += l2tp_ip.o
|
||||||
|
|
|
@ -36,8 +36,10 @@
|
||||||
#include <linux/inetdevice.h>
|
#include <linux/inetdevice.h>
|
||||||
#include <linux/skbuff.h>
|
#include <linux/skbuff.h>
|
||||||
#include <linux/init.h>
|
#include <linux/init.h>
|
||||||
|
#include <linux/in.h>
|
||||||
#include <linux/ip.h>
|
#include <linux/ip.h>
|
||||||
#include <linux/udp.h>
|
#include <linux/udp.h>
|
||||||
|
#include <linux/l2tp.h>
|
||||||
#include <linux/hash.h>
|
#include <linux/hash.h>
|
||||||
#include <linux/sort.h>
|
#include <linux/sort.h>
|
||||||
#include <linux/file.h>
|
#include <linux/file.h>
|
||||||
|
@ -48,6 +50,7 @@
|
||||||
#include <net/ip.h>
|
#include <net/ip.h>
|
||||||
#include <net/udp.h>
|
#include <net/udp.h>
|
||||||
#include <net/xfrm.h>
|
#include <net/xfrm.h>
|
||||||
|
#include <net/protocol.h>
|
||||||
|
|
||||||
#include <asm/byteorder.h>
|
#include <asm/byteorder.h>
|
||||||
#include <asm/atomic.h>
|
#include <asm/atomic.h>
|
||||||
|
@ -849,15 +852,21 @@ static int l2tp_build_l2tpv2_header(struct l2tp_session *session, void *buf)
|
||||||
|
|
||||||
static int l2tp_build_l2tpv3_header(struct l2tp_session *session, void *buf)
|
static int l2tp_build_l2tpv3_header(struct l2tp_session *session, void *buf)
|
||||||
{
|
{
|
||||||
|
struct l2tp_tunnel *tunnel = session->tunnel;
|
||||||
char *bufp = buf;
|
char *bufp = buf;
|
||||||
char *optr = bufp;
|
char *optr = bufp;
|
||||||
u16 flags = L2TP_HDR_VER_3;
|
|
||||||
|
|
||||||
/* Setup L2TP header. */
|
/* Setup L2TP header. The header differs slightly for UDP and
|
||||||
*((__be16 *) bufp) = htons(flags);
|
* IP encapsulations. For UDP, there is 4 bytes of flags.
|
||||||
bufp += 2;
|
*/
|
||||||
*((__be16 *) bufp) = 0;
|
if (tunnel->encap == L2TP_ENCAPTYPE_UDP) {
|
||||||
bufp += 2;
|
u16 flags = L2TP_HDR_VER_3;
|
||||||
|
*((__be16 *) bufp) = htons(flags);
|
||||||
|
bufp += 2;
|
||||||
|
*((__be16 *) bufp) = 0;
|
||||||
|
bufp += 2;
|
||||||
|
}
|
||||||
|
|
||||||
*((__be32 *) bufp) = htonl(session->peer_session_id);
|
*((__be32 *) bufp) = htonl(session->peer_session_id);
|
||||||
bufp += 4;
|
bufp += 4;
|
||||||
if (session->cookie_len) {
|
if (session->cookie_len) {
|
||||||
|
@ -902,10 +911,11 @@ int l2tp_xmit_core(struct l2tp_session *session, struct sk_buff *skb, size_t dat
|
||||||
|
|
||||||
if (session->debug & L2TP_MSG_DATA) {
|
if (session->debug & L2TP_MSG_DATA) {
|
||||||
int i;
|
int i;
|
||||||
unsigned char *datap = skb->data + sizeof(struct udphdr);
|
int uhlen = (tunnel->encap == L2TP_ENCAPTYPE_UDP) ? sizeof(struct udphdr) : 0;
|
||||||
|
unsigned char *datap = skb->data + uhlen;
|
||||||
|
|
||||||
printk(KERN_DEBUG "%s: xmit:", session->name);
|
printk(KERN_DEBUG "%s: xmit:", session->name);
|
||||||
for (i = 0; i < (len - sizeof(struct udphdr)); i++) {
|
for (i = 0; i < (len - uhlen); i++) {
|
||||||
printk(" %02X", *datap++);
|
printk(" %02X", *datap++);
|
||||||
if (i == 31) {
|
if (i == 31) {
|
||||||
printk(" ...");
|
printk(" ...");
|
||||||
|
@ -956,21 +966,23 @@ static inline void l2tp_skb_set_owner_w(struct sk_buff *skb, struct sock *sk)
|
||||||
int l2tp_xmit_skb(struct l2tp_session *session, struct sk_buff *skb, int hdr_len)
|
int l2tp_xmit_skb(struct l2tp_session *session, struct sk_buff *skb, int hdr_len)
|
||||||
{
|
{
|
||||||
int data_len = skb->len;
|
int data_len = skb->len;
|
||||||
struct sock *sk = session->tunnel->sock;
|
struct l2tp_tunnel *tunnel = session->tunnel;
|
||||||
|
struct sock *sk = tunnel->sock;
|
||||||
struct udphdr *uh;
|
struct udphdr *uh;
|
||||||
unsigned int udp_len;
|
|
||||||
struct inet_sock *inet;
|
struct inet_sock *inet;
|
||||||
__wsum csum;
|
__wsum csum;
|
||||||
int old_headroom;
|
int old_headroom;
|
||||||
int new_headroom;
|
int new_headroom;
|
||||||
int headroom;
|
int headroom;
|
||||||
|
int uhlen = (tunnel->encap == L2TP_ENCAPTYPE_UDP) ? sizeof(struct udphdr) : 0;
|
||||||
|
int udp_len;
|
||||||
|
|
||||||
/* Check that there's enough headroom in the skb to insert IP,
|
/* Check that there's enough headroom in the skb to insert IP,
|
||||||
* UDP and L2TP headers. If not enough, expand it to
|
* UDP and L2TP headers. If not enough, expand it to
|
||||||
* make room. Adjust truesize.
|
* make room. Adjust truesize.
|
||||||
*/
|
*/
|
||||||
headroom = NET_SKB_PAD + sizeof(struct iphdr) +
|
headroom = NET_SKB_PAD + sizeof(struct iphdr) +
|
||||||
sizeof(struct udphdr) + hdr_len;
|
uhlen + hdr_len;
|
||||||
old_headroom = skb_headroom(skb);
|
old_headroom = skb_headroom(skb);
|
||||||
if (skb_cow_head(skb, headroom))
|
if (skb_cow_head(skb, headroom))
|
||||||
goto abort;
|
goto abort;
|
||||||
|
@ -981,18 +993,8 @@ int l2tp_xmit_skb(struct l2tp_session *session, struct sk_buff *skb, int hdr_len
|
||||||
|
|
||||||
/* Setup L2TP header */
|
/* Setup L2TP header */
|
||||||
session->build_header(session, __skb_push(skb, hdr_len));
|
session->build_header(session, __skb_push(skb, hdr_len));
|
||||||
udp_len = sizeof(struct udphdr) + hdr_len + data_len;
|
|
||||||
|
|
||||||
/* Setup UDP header */
|
|
||||||
inet = inet_sk(sk);
|
|
||||||
__skb_push(skb, sizeof(*uh));
|
|
||||||
skb_reset_transport_header(skb);
|
|
||||||
uh = udp_hdr(skb);
|
|
||||||
uh->source = inet->inet_sport;
|
|
||||||
uh->dest = inet->inet_dport;
|
|
||||||
uh->len = htons(udp_len);
|
|
||||||
uh->check = 0;
|
|
||||||
|
|
||||||
|
/* Reset skb netfilter state */
|
||||||
memset(&(IPCB(skb)->opt), 0, sizeof(IPCB(skb)->opt));
|
memset(&(IPCB(skb)->opt), 0, sizeof(IPCB(skb)->opt));
|
||||||
IPCB(skb)->flags &= ~(IPSKB_XFRM_TUNNEL_SIZE | IPSKB_XFRM_TRANSFORMED |
|
IPCB(skb)->flags &= ~(IPSKB_XFRM_TUNNEL_SIZE | IPSKB_XFRM_TRANSFORMED |
|
||||||
IPSKB_REROUTED);
|
IPSKB_REROUTED);
|
||||||
|
@ -1001,29 +1003,48 @@ int l2tp_xmit_skb(struct l2tp_session *session, struct sk_buff *skb, int hdr_len
|
||||||
/* Get routing info from the tunnel socket */
|
/* Get routing info from the tunnel socket */
|
||||||
skb_dst_drop(skb);
|
skb_dst_drop(skb);
|
||||||
skb_dst_set(skb, dst_clone(__sk_dst_get(sk)));
|
skb_dst_set(skb, dst_clone(__sk_dst_get(sk)));
|
||||||
l2tp_skb_set_owner_w(skb, sk);
|
|
||||||
|
|
||||||
/* Calculate UDP checksum if configured to do so */
|
switch (tunnel->encap) {
|
||||||
if (sk->sk_no_check == UDP_CSUM_NOXMIT)
|
case L2TP_ENCAPTYPE_UDP:
|
||||||
skb->ip_summed = CHECKSUM_NONE;
|
/* Setup UDP header */
|
||||||
else if ((skb_dst(skb) && skb_dst(skb)->dev) &&
|
inet = inet_sk(sk);
|
||||||
(!(skb_dst(skb)->dev->features & NETIF_F_V4_CSUM))) {
|
__skb_push(skb, sizeof(*uh));
|
||||||
skb->ip_summed = CHECKSUM_COMPLETE;
|
skb_reset_transport_header(skb);
|
||||||
csum = skb_checksum(skb, 0, udp_len, 0);
|
uh = udp_hdr(skb);
|
||||||
uh->check = csum_tcpudp_magic(inet->inet_saddr,
|
uh->source = inet->inet_sport;
|
||||||
inet->inet_daddr,
|
uh->dest = inet->inet_dport;
|
||||||
udp_len, IPPROTO_UDP, csum);
|
udp_len = uhlen + hdr_len + data_len;
|
||||||
if (uh->check == 0)
|
uh->len = htons(udp_len);
|
||||||
uh->check = CSUM_MANGLED_0;
|
uh->check = 0;
|
||||||
} else {
|
|
||||||
skb->ip_summed = CHECKSUM_PARTIAL;
|
/* Calculate UDP checksum if configured to do so */
|
||||||
skb->csum_start = skb_transport_header(skb) - skb->head;
|
if (sk->sk_no_check == UDP_CSUM_NOXMIT)
|
||||||
skb->csum_offset = offsetof(struct udphdr, check);
|
skb->ip_summed = CHECKSUM_NONE;
|
||||||
uh->check = ~csum_tcpudp_magic(inet->inet_saddr,
|
else if ((skb_dst(skb) && skb_dst(skb)->dev) &&
|
||||||
inet->inet_daddr,
|
(!(skb_dst(skb)->dev->features & NETIF_F_V4_CSUM))) {
|
||||||
udp_len, IPPROTO_UDP, 0);
|
skb->ip_summed = CHECKSUM_COMPLETE;
|
||||||
|
csum = skb_checksum(skb, 0, udp_len, 0);
|
||||||
|
uh->check = csum_tcpudp_magic(inet->inet_saddr,
|
||||||
|
inet->inet_daddr,
|
||||||
|
udp_len, IPPROTO_UDP, csum);
|
||||||
|
if (uh->check == 0)
|
||||||
|
uh->check = CSUM_MANGLED_0;
|
||||||
|
} else {
|
||||||
|
skb->ip_summed = CHECKSUM_PARTIAL;
|
||||||
|
skb->csum_start = skb_transport_header(skb) - skb->head;
|
||||||
|
skb->csum_offset = offsetof(struct udphdr, check);
|
||||||
|
uh->check = ~csum_tcpudp_magic(inet->inet_saddr,
|
||||||
|
inet->inet_daddr,
|
||||||
|
udp_len, IPPROTO_UDP, 0);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case L2TP_ENCAPTYPE_IP:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
l2tp_skb_set_owner_w(skb, sk);
|
||||||
|
|
||||||
l2tp_xmit_core(session, skb, data_len);
|
l2tp_xmit_core(session, skb, data_len);
|
||||||
|
|
||||||
abort:
|
abort:
|
||||||
|
@ -1053,9 +1074,15 @@ void l2tp_tunnel_destruct(struct sock *sk)
|
||||||
/* Close all sessions */
|
/* Close all sessions */
|
||||||
l2tp_tunnel_closeall(tunnel);
|
l2tp_tunnel_closeall(tunnel);
|
||||||
|
|
||||||
/* No longer an encapsulation socket. See net/ipv4/udp.c */
|
switch (tunnel->encap) {
|
||||||
(udp_sk(sk))->encap_type = 0;
|
case L2TP_ENCAPTYPE_UDP:
|
||||||
(udp_sk(sk))->encap_rcv = NULL;
|
/* No longer an encapsulation socket. See net/ipv4/udp.c */
|
||||||
|
(udp_sk(sk))->encap_type = 0;
|
||||||
|
(udp_sk(sk))->encap_rcv = NULL;
|
||||||
|
break;
|
||||||
|
case L2TP_ENCAPTYPE_IP:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
/* Remove hooks into tunnel socket */
|
/* Remove hooks into tunnel socket */
|
||||||
tunnel->sock = NULL;
|
tunnel->sock = NULL;
|
||||||
|
@ -1168,6 +1195,7 @@ int l2tp_tunnel_create(struct net *net, int fd, int version, u32 tunnel_id, u32
|
||||||
struct socket *sock = NULL;
|
struct socket *sock = NULL;
|
||||||
struct sock *sk = NULL;
|
struct sock *sk = NULL;
|
||||||
struct l2tp_net *pn;
|
struct l2tp_net *pn;
|
||||||
|
enum l2tp_encap_type encap = L2TP_ENCAPTYPE_UDP;
|
||||||
|
|
||||||
/* Get the tunnel socket from the fd, which was opened by
|
/* Get the tunnel socket from the fd, which was opened by
|
||||||
* the userspace L2TP daemon.
|
* the userspace L2TP daemon.
|
||||||
|
@ -1182,18 +1210,27 @@ int l2tp_tunnel_create(struct net *net, int fd, int version, u32 tunnel_id, u32
|
||||||
|
|
||||||
sk = sock->sk;
|
sk = sock->sk;
|
||||||
|
|
||||||
|
if (cfg != NULL)
|
||||||
|
encap = cfg->encap;
|
||||||
|
|
||||||
/* Quick sanity checks */
|
/* Quick sanity checks */
|
||||||
err = -EPROTONOSUPPORT;
|
switch (encap) {
|
||||||
if (sk->sk_protocol != IPPROTO_UDP) {
|
case L2TP_ENCAPTYPE_UDP:
|
||||||
printk(KERN_ERR "tunl %hu: fd %d wrong protocol, got %d, expected %d\n",
|
err = -EPROTONOSUPPORT;
|
||||||
tunnel_id, fd, sk->sk_protocol, IPPROTO_UDP);
|
if (sk->sk_protocol != IPPROTO_UDP) {
|
||||||
goto err;
|
printk(KERN_ERR "tunl %hu: fd %d wrong protocol, got %d, expected %d\n",
|
||||||
}
|
tunnel_id, fd, sk->sk_protocol, IPPROTO_UDP);
|
||||||
err = -EAFNOSUPPORT;
|
goto err;
|
||||||
if (sock->ops->family != AF_INET) {
|
}
|
||||||
printk(KERN_ERR "tunl %hu: fd %d wrong family, got %d, expected %d\n",
|
break;
|
||||||
tunnel_id, fd, sock->ops->family, AF_INET);
|
case L2TP_ENCAPTYPE_IP:
|
||||||
goto err;
|
err = -EPROTONOSUPPORT;
|
||||||
|
if (sk->sk_protocol != IPPROTO_L2TP) {
|
||||||
|
printk(KERN_ERR "tunl %hu: fd %d wrong protocol, got %d, expected %d\n",
|
||||||
|
tunnel_id, fd, sk->sk_protocol, IPPROTO_L2TP);
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check if this socket has already been prepped */
|
/* Check if this socket has already been prepped */
|
||||||
|
@ -1223,12 +1260,16 @@ int l2tp_tunnel_create(struct net *net, int fd, int version, u32 tunnel_id, u32
|
||||||
tunnel->l2tp_net = net;
|
tunnel->l2tp_net = net;
|
||||||
pn = l2tp_pernet(net);
|
pn = l2tp_pernet(net);
|
||||||
|
|
||||||
if (cfg)
|
if (cfg != NULL)
|
||||||
tunnel->debug = cfg->debug;
|
tunnel->debug = cfg->debug;
|
||||||
|
|
||||||
/* Mark socket as an encapsulation socket. See net/ipv4/udp.c */
|
/* Mark socket as an encapsulation socket. See net/ipv4/udp.c */
|
||||||
udp_sk(sk)->encap_type = UDP_ENCAP_L2TPINUDP;
|
tunnel->encap = encap;
|
||||||
udp_sk(sk)->encap_rcv = l2tp_udp_encap_recv;
|
if (encap == L2TP_ENCAPTYPE_UDP) {
|
||||||
|
/* Mark socket as an encapsulation socket. See net/ipv4/udp.c */
|
||||||
|
udp_sk(sk)->encap_type = UDP_ENCAP_L2TPINUDP;
|
||||||
|
udp_sk(sk)->encap_rcv = l2tp_udp_encap_recv;
|
||||||
|
}
|
||||||
|
|
||||||
sk->sk_user_data = tunnel;
|
sk->sk_user_data = tunnel;
|
||||||
|
|
||||||
|
@ -1318,7 +1359,9 @@ void l2tp_session_set_header_len(struct l2tp_session *session, int version)
|
||||||
if (session->send_seq)
|
if (session->send_seq)
|
||||||
session->hdr_len += 4;
|
session->hdr_len += 4;
|
||||||
} else {
|
} else {
|
||||||
session->hdr_len = 8 + session->cookie_len + session->l2specific_len + session->offset;
|
session->hdr_len = 4 + session->cookie_len + session->l2specific_len + session->offset;
|
||||||
|
if (session->tunnel->encap == L2TP_ENCAPTYPE_UDP)
|
||||||
|
session->hdr_len += 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,11 @@ enum l2tp_l2spec_type {
|
||||||
L2TP_L2SPECTYPE_DEFAULT,
|
L2TP_L2SPECTYPE_DEFAULT,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum l2tp_encap_type {
|
||||||
|
L2TP_ENCAPTYPE_UDP,
|
||||||
|
L2TP_ENCAPTYPE_IP,
|
||||||
|
};
|
||||||
|
|
||||||
struct sk_buff;
|
struct sk_buff;
|
||||||
|
|
||||||
struct l2tp_stats {
|
struct l2tp_stats {
|
||||||
|
@ -155,6 +160,7 @@ struct l2tp_session {
|
||||||
struct l2tp_tunnel_cfg {
|
struct l2tp_tunnel_cfg {
|
||||||
int debug; /* bitmask of debug message
|
int debug; /* bitmask of debug message
|
||||||
* categories */
|
* categories */
|
||||||
|
enum l2tp_encap_type encap;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct l2tp_tunnel {
|
struct l2tp_tunnel {
|
||||||
|
@ -170,6 +176,7 @@ struct l2tp_tunnel {
|
||||||
char name[20]; /* for logging */
|
char name[20]; /* for logging */
|
||||||
int debug; /* bitmask of debug message
|
int debug; /* bitmask of debug message
|
||||||
* categories */
|
* categories */
|
||||||
|
enum l2tp_encap_type encap;
|
||||||
struct l2tp_stats stats;
|
struct l2tp_stats stats;
|
||||||
|
|
||||||
struct list_head list; /* Keep a list of all tunnels */
|
struct list_head list; /* Keep a list of all tunnels */
|
||||||
|
|
679
net/l2tp/l2tp_ip.c
Normal file
679
net/l2tp/l2tp_ip.c
Normal file
|
@ -0,0 +1,679 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#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;
|
||||||
|
|
||||||
|
__u64 tx_packets;
|
||||||
|
__u64 tx_bytes;
|
||||||
|
__u64 tx_errors;
|
||||||
|
__u64 rx_packets;
|
||||||
|
__u64 rx_bytes;
|
||||||
|
__u64 rx_errors;
|
||||||
|
};
|
||||||
|
|
||||||
|
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 hlist_node *node;
|
||||||
|
struct sock *sk;
|
||||||
|
|
||||||
|
sk_for_each_bound(sk, node, &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) &&
|
||||||
|
#ifdef CONFIG_NET_NS
|
||||||
|
(sk->sk_net == net) &&
|
||||||
|
#endif
|
||||||
|
!(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 sock *sk;
|
||||||
|
u32 session_id;
|
||||||
|
u32 tunnel_id;
|
||||||
|
unsigned char *ptr, *optr;
|
||||||
|
struct l2tp_session *session;
|
||||||
|
struct l2tp_tunnel *tunnel = NULL;
|
||||||
|
int length;
|
||||||
|
int offset;
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
|
||||||
|
printk(KERN_DEBUG "%s: ip recv: ", tunnel->name);
|
||||||
|
|
||||||
|
offset = 0;
|
||||||
|
do {
|
||||||
|
printk(" %02X", ptr[offset]);
|
||||||
|
} while (++offset < length);
|
||||||
|
|
||||||
|
printk("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
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 iphdr *iph = (struct iphdr *) skb_network_header(skb);
|
||||||
|
|
||||||
|
read_lock_bh(&l2tp_ip_lock);
|
||||||
|
sk = __l2tp_ip_bind_lookup(&init_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);
|
||||||
|
hlist_del_init(&sk->sk_node);
|
||||||
|
write_unlock_bh(&l2tp_ip_lock);
|
||||||
|
sk_common_release(sk);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void l2tp_ip_destroy_sock(struct sock *sk)
|
||||||
|
{
|
||||||
|
struct sk_buff *skb;
|
||||||
|
|
||||||
|
while ((skb = __skb_dequeue_tail(&sk->sk_write_queue)) != NULL)
|
||||||
|
kfree_skb(skb);
|
||||||
|
|
||||||
|
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;
|
||||||
|
int ret = -EINVAL;
|
||||||
|
int chk_addr_ret;
|
||||||
|
|
||||||
|
ret = -EADDRINUSE;
|
||||||
|
read_lock_bh(&l2tp_ip_lock);
|
||||||
|
if (__l2tp_ip_bind_lookup(&init_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(&init_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;
|
||||||
|
|
||||||
|
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;
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
struct inet_sock *inet = inet_sk(sk);
|
||||||
|
struct sockaddr_l2tpip *lsa = (struct sockaddr_l2tpip *) uaddr;
|
||||||
|
struct rtable *rt;
|
||||||
|
__be32 saddr;
|
||||||
|
int oif;
|
||||||
|
|
||||||
|
rc = -EINVAL;
|
||||||
|
if (addr_len < sizeof(*lsa))
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
rc = -EAFNOSUPPORT;
|
||||||
|
if (lsa->l2tp_family != AF_INET)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
sk_dst_reset(sk);
|
||||||
|
|
||||||
|
oif = sk->sk_bound_dev_if;
|
||||||
|
saddr = inet->inet_saddr;
|
||||||
|
|
||||||
|
rc = -EINVAL;
|
||||||
|
if (ipv4_is_multicast(lsa->l2tp_addr.s_addr))
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
rc = ip_route_connect(&rt, lsa->l2tp_addr.s_addr, saddr,
|
||||||
|
RT_CONN_FLAGS(sk), oif,
|
||||||
|
IPPROTO_L2TP,
|
||||||
|
0, 0, sk, 1);
|
||||||
|
if (rc) {
|
||||||
|
if (rc == -ENETUNREACH)
|
||||||
|
IP_INC_STATS_BH(&init_net, IPSTATS_MIB_OUTNOROUTES);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = -ENETUNREACH;
|
||||||
|
if (rt->rt_flags & (RTCF_MULTICAST | RTCF_BROADCAST)) {
|
||||||
|
ip_rt_put(rt);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
l2tp_ip_sk(sk)->peer_conn_id = lsa->l2tp_conn_id;
|
||||||
|
|
||||||
|
if (!inet->inet_saddr)
|
||||||
|
inet->inet_saddr = rt->rt_src;
|
||||||
|
if (!inet->inet_rcv_saddr)
|
||||||
|
inet->inet_rcv_saddr = rt->rt_src;
|
||||||
|
inet->inet_daddr = rt->rt_dst;
|
||||||
|
sk->sk_state = TCP_ESTABLISHED;
|
||||||
|
inet->inet_id = jiffies;
|
||||||
|
|
||||||
|
sk_dst_set(sk, &rt->u.dst);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
rc = 0;
|
||||||
|
out:
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
if (!xfrm4_policy_check(sk, XFRM_POLICY_IN, skb))
|
||||||
|
goto drop;
|
||||||
|
|
||||||
|
nf_reset(skb);
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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 l2tp_ip_sock *lsa = l2tp_ip_sk(sk);
|
||||||
|
struct inet_sock *inet = inet_sk(sk);
|
||||||
|
struct ip_options *opt = inet->opt;
|
||||||
|
struct rtable *rt = NULL;
|
||||||
|
int connected = 0;
|
||||||
|
__be32 daddr;
|
||||||
|
|
||||||
|
if (sock_flag(sk, SOCK_DEAD))
|
||||||
|
return -ENOTCONN;
|
||||||
|
|
||||||
|
/* Get and verify the address. */
|
||||||
|
if (msg->msg_name) {
|
||||||
|
struct sockaddr_l2tpip *lip = (struct sockaddr_l2tpip *) msg->msg_name;
|
||||||
|
if (msg->msg_namelen < sizeof(*lip))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if (lip->l2tp_family != AF_INET) {
|
||||||
|
if (lip->l2tp_family != AF_UNSPEC)
|
||||||
|
return -EAFNOSUPPORT;
|
||||||
|
}
|
||||||
|
|
||||||
|
daddr = lip->l2tp_addr.s_addr;
|
||||||
|
} else {
|
||||||
|
if (sk->sk_state != TCP_ESTABLISHED)
|
||||||
|
return -EDESTADDRREQ;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (connected)
|
||||||
|
rt = (struct rtable *) __sk_dst_check(sk, 0);
|
||||||
|
|
||||||
|
if (rt == NULL) {
|
||||||
|
/* Use correct destination address if we have options. */
|
||||||
|
if (opt && opt->srr)
|
||||||
|
daddr = opt->faddr;
|
||||||
|
|
||||||
|
{
|
||||||
|
struct flowi fl = { .oif = sk->sk_bound_dev_if,
|
||||||
|
.nl_u = { .ip4_u = {
|
||||||
|
.daddr = daddr,
|
||||||
|
.saddr = inet->inet_saddr,
|
||||||
|
.tos = RT_CONN_FLAGS(sk) } },
|
||||||
|
.proto = sk->sk_protocol,
|
||||||
|
.flags = inet_sk_flowi_flags(sk),
|
||||||
|
.uli_u = { .ports = {
|
||||||
|
.sport = inet->inet_sport,
|
||||||
|
.dport = inet->inet_dport } } };
|
||||||
|
|
||||||
|
/* If this fails, retransmit mechanism of transport layer will
|
||||||
|
* keep trying until route appears or the connection times
|
||||||
|
* itself out.
|
||||||
|
*/
|
||||||
|
security_sk_classify_flow(sk, &fl);
|
||||||
|
if (ip_route_output_flow(sock_net(sk), &rt, &fl, sk, 0))
|
||||||
|
goto no_route;
|
||||||
|
}
|
||||||
|
sk_setup_caps(sk, &rt->u.dst);
|
||||||
|
}
|
||||||
|
skb_dst_set(skb, dst_clone(&rt->u.dst));
|
||||||
|
|
||||||
|
/* Queue the packet to IP for output */
|
||||||
|
rc = ip_queue_xmit(skb, 0);
|
||||||
|
|
||||||
|
error:
|
||||||
|
/* Update stats */
|
||||||
|
if (rc >= 0) {
|
||||||
|
lsa->tx_packets++;
|
||||||
|
lsa->tx_bytes += len;
|
||||||
|
rc = len;
|
||||||
|
} else {
|
||||||
|
lsa->tx_errors++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
|
||||||
|
no_route:
|
||||||
|
IP_INC_STATS(sock_net(sk), IPSTATS_MIB_OUTNOROUTES);
|
||||||
|
kfree_skb(skb);
|
||||||
|
return -EHOSTUNREACH;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
struct l2tp_ip_sock *lsk = l2tp_ip_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;
|
||||||
|
|
||||||
|
if (addr_len)
|
||||||
|
*addr_len = sizeof(*sin);
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
if (inet->cmsg_flags)
|
||||||
|
ip_cmsg_recv(msg, skb);
|
||||||
|
if (flags & MSG_TRUNC)
|
||||||
|
copied = skb->len;
|
||||||
|
done:
|
||||||
|
skb_free_datagram(sk, skb);
|
||||||
|
out:
|
||||||
|
if (err) {
|
||||||
|
lsk->rx_errors++;
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
lsk->rx_packets++;
|
||||||
|
lsk->rx_bytes += copied;
|
||||||
|
|
||||||
|
return copied;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = udp_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,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int __init l2tp_ip_init(void)
|
||||||
|
{
|
||||||
|
int err;
|
||||||
|
|
||||||
|
printk(KERN_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");
|
||||||
|
MODULE_ALIAS_NET_PF_PROTO_TYPE(PF_INET, SOCK_DGRAM, IPPROTO_L2TP);
|
|
@ -305,6 +305,7 @@ static int pppol2tp_sendmsg(struct kiocb *iocb, struct socket *sock, struct msgh
|
||||||
struct l2tp_session *session;
|
struct l2tp_session *session;
|
||||||
struct l2tp_tunnel *tunnel;
|
struct l2tp_tunnel *tunnel;
|
||||||
struct pppol2tp_session *ps;
|
struct pppol2tp_session *ps;
|
||||||
|
int uhlen;
|
||||||
|
|
||||||
error = -ENOTCONN;
|
error = -ENOTCONN;
|
||||||
if (sock_flag(sk, SOCK_DEAD) || !(sk->sk_state & PPPOX_CONNECTED))
|
if (sock_flag(sk, SOCK_DEAD) || !(sk->sk_state & PPPOX_CONNECTED))
|
||||||
|
@ -321,10 +322,12 @@ static int pppol2tp_sendmsg(struct kiocb *iocb, struct socket *sock, struct msgh
|
||||||
if (tunnel == NULL)
|
if (tunnel == NULL)
|
||||||
goto error_put_sess;
|
goto error_put_sess;
|
||||||
|
|
||||||
|
uhlen = (tunnel->encap == L2TP_ENCAPTYPE_UDP) ? sizeof(struct udphdr) : 0;
|
||||||
|
|
||||||
/* Allocate a socket buffer */
|
/* Allocate a socket buffer */
|
||||||
error = -ENOMEM;
|
error = -ENOMEM;
|
||||||
skb = sock_wmalloc(sk, NET_SKB_PAD + sizeof(struct iphdr) +
|
skb = sock_wmalloc(sk, NET_SKB_PAD + sizeof(struct iphdr) +
|
||||||
sizeof(struct udphdr) + session->hdr_len +
|
uhlen + session->hdr_len +
|
||||||
sizeof(ppph) + total_len,
|
sizeof(ppph) + total_len,
|
||||||
0, GFP_KERNEL);
|
0, GFP_KERNEL);
|
||||||
if (!skb)
|
if (!skb)
|
||||||
|
@ -335,7 +338,7 @@ static int pppol2tp_sendmsg(struct kiocb *iocb, struct socket *sock, struct msgh
|
||||||
skb_reset_network_header(skb);
|
skb_reset_network_header(skb);
|
||||||
skb_reserve(skb, sizeof(struct iphdr));
|
skb_reserve(skb, sizeof(struct iphdr));
|
||||||
skb_reset_transport_header(skb);
|
skb_reset_transport_header(skb);
|
||||||
skb_reserve(skb, sizeof(struct udphdr));
|
skb_reserve(skb, uhlen);
|
||||||
|
|
||||||
/* Add PPP header */
|
/* Add PPP header */
|
||||||
skb->data[0] = ppph[0];
|
skb->data[0] = ppph[0];
|
||||||
|
|
Loading…
Reference in a new issue