diff options
author | James Chapman <jchapman@katalix.com> | 2010-04-02 10:19:00 +0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2010-04-04 01:56:04 +0400 |
commit | 0d76751fad7739014485ba5bd388d4f1b4fd4143 (patch) | |
tree | 25a4525bf6b2ec9f052f22ba98cdfd3ff3a86aa3 /net/l2tp/l2tp_core.c | |
parent | e0d4435f93905f517003cfa7328a36ea19788147 (diff) | |
download | linux-0d76751fad7739014485ba5bd388d4f1b4fd4143.tar.xz |
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>
Diffstat (limited to 'net/l2tp/l2tp_core.c')
-rw-r--r-- | net/l2tp/l2tp_core.c | 163 |
1 files changed, 103 insertions, 60 deletions
diff --git a/net/l2tp/l2tp_core.c b/net/l2tp/l2tp_core.c index 0eee1a65f1b1..1739d04367e4 100644 --- a/net/l2tp/l2tp_core.c +++ b/net/l2tp/l2tp_core.c @@ -36,8 +36,10 @@ #include <linux/inetdevice.h> #include <linux/skbuff.h> #include <linux/init.h> +#include <linux/in.h> #include <linux/ip.h> #include <linux/udp.h> +#include <linux/l2tp.h> #include <linux/hash.h> #include <linux/sort.h> #include <linux/file.h> @@ -48,6 +50,7 @@ #include <net/ip.h> #include <net/udp.h> #include <net/xfrm.h> +#include <net/protocol.h> #include <asm/byteorder.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) { + struct l2tp_tunnel *tunnel = session->tunnel; char *bufp = buf; char *optr = bufp; - u16 flags = L2TP_HDR_VER_3; - /* Setup L2TP header. */ - *((__be16 *) bufp) = htons(flags); - bufp += 2; - *((__be16 *) bufp) = 0; - bufp += 2; + /* Setup L2TP header. The header differs slightly for UDP and + * IP encapsulations. For UDP, there is 4 bytes of flags. + */ + if (tunnel->encap == L2TP_ENCAPTYPE_UDP) { + u16 flags = L2TP_HDR_VER_3; + *((__be16 *) bufp) = htons(flags); + bufp += 2; + *((__be16 *) bufp) = 0; + bufp += 2; + } + *((__be32 *) bufp) = htonl(session->peer_session_id); bufp += 4; 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) { 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); - for (i = 0; i < (len - sizeof(struct udphdr)); i++) { + for (i = 0; i < (len - uhlen); i++) { printk(" %02X", *datap++); if (i == 31) { 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 data_len = skb->len; - struct sock *sk = session->tunnel->sock; + struct l2tp_tunnel *tunnel = session->tunnel; + struct sock *sk = tunnel->sock; struct udphdr *uh; - unsigned int udp_len; struct inet_sock *inet; __wsum csum; int old_headroom; int new_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, * UDP and L2TP headers. If not enough, expand it to * make room. Adjust truesize. */ headroom = NET_SKB_PAD + sizeof(struct iphdr) + - sizeof(struct udphdr) + hdr_len; + uhlen + hdr_len; old_headroom = skb_headroom(skb); if (skb_cow_head(skb, headroom)) goto abort; @@ -981,18 +993,8 @@ int l2tp_xmit_skb(struct l2tp_session *session, struct sk_buff *skb, int hdr_len /* Setup L2TP header */ 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)); IPCB(skb)->flags &= ~(IPSKB_XFRM_TUNNEL_SIZE | IPSKB_XFRM_TRANSFORMED | 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 */ skb_dst_drop(skb); 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 */ - if (sk->sk_no_check == UDP_CSUM_NOXMIT) - skb->ip_summed = CHECKSUM_NONE; - else if ((skb_dst(skb) && skb_dst(skb)->dev) && - (!(skb_dst(skb)->dev->features & NETIF_F_V4_CSUM))) { - 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); + switch (tunnel->encap) { + case L2TP_ENCAPTYPE_UDP: + /* 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; + udp_len = uhlen + hdr_len + data_len; + uh->len = htons(udp_len); + uh->check = 0; + + /* Calculate UDP checksum if configured to do so */ + if (sk->sk_no_check == UDP_CSUM_NOXMIT) + skb->ip_summed = CHECKSUM_NONE; + else if ((skb_dst(skb) && skb_dst(skb)->dev) && + (!(skb_dst(skb)->dev->features & NETIF_F_V4_CSUM))) { + 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); abort: @@ -1053,9 +1074,15 @@ void l2tp_tunnel_destruct(struct sock *sk) /* Close all sessions */ l2tp_tunnel_closeall(tunnel); - /* No longer an encapsulation socket. See net/ipv4/udp.c */ - (udp_sk(sk))->encap_type = 0; - (udp_sk(sk))->encap_rcv = NULL; + switch (tunnel->encap) { + case L2TP_ENCAPTYPE_UDP: + /* 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 */ 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 sock *sk = NULL; struct l2tp_net *pn; + enum l2tp_encap_type encap = L2TP_ENCAPTYPE_UDP; /* Get the tunnel socket from the fd, which was opened by * 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; + if (cfg != NULL) + encap = cfg->encap; + /* Quick sanity checks */ - err = -EPROTONOSUPPORT; - if (sk->sk_protocol != IPPROTO_UDP) { - printk(KERN_ERR "tunl %hu: fd %d wrong protocol, got %d, expected %d\n", - tunnel_id, fd, sk->sk_protocol, IPPROTO_UDP); - goto err; - } - err = -EAFNOSUPPORT; - if (sock->ops->family != AF_INET) { - printk(KERN_ERR "tunl %hu: fd %d wrong family, got %d, expected %d\n", - tunnel_id, fd, sock->ops->family, AF_INET); - goto err; + switch (encap) { + case L2TP_ENCAPTYPE_UDP: + err = -EPROTONOSUPPORT; + if (sk->sk_protocol != IPPROTO_UDP) { + printk(KERN_ERR "tunl %hu: fd %d wrong protocol, got %d, expected %d\n", + tunnel_id, fd, sk->sk_protocol, IPPROTO_UDP); + goto err; + } + break; + case L2TP_ENCAPTYPE_IP: + 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 */ @@ -1223,12 +1260,16 @@ int l2tp_tunnel_create(struct net *net, int fd, int version, u32 tunnel_id, u32 tunnel->l2tp_net = net; pn = l2tp_pernet(net); - if (cfg) + if (cfg != NULL) tunnel->debug = cfg->debug; /* 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; + tunnel->encap = encap; + 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; @@ -1318,7 +1359,9 @@ void l2tp_session_set_header_len(struct l2tp_session *session, int version) if (session->send_seq) session->hdr_len += 4; } 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; } } |