diff mbox

[4/4] net/l2tp: add support for L2TP over IPv6 UDP encap

Message ID 20120418134438.GA5162@kvack.org
State Changes Requested, archived
Delegated to: David Miller
Headers show

Commit Message

Benjamin LaHaise April 18, 2012, 1:44 p.m. UTC
Now that encap_rcv() works on IPv6 UDP sockets, wire L2TP up to IPv6.
Support has been tested with and without hardware offloading.

Signed-off-by: Benjamin LaHaise <bcrl@kvack.org>
---
 include/linux/if_pppol2tp.h |   28 +++++++++++++-
 include/linux/if_pppox.h    |   12 ++++++
 net/l2tp/l2tp_core.c        |   89 +++++++++++++++++++++++++++++++++++++------
 net/l2tp/l2tp_ppp.c         |   42 ++++++++++++++++++++-
 4 files changed, 157 insertions(+), 14 deletions(-)

Comments

James Chapman April 18, 2012, 3:36 p.m. UTC | #1
On 18/04/12 14:44, Benjamin LaHaise wrote:
> Now that encap_rcv() works on IPv6 UDP sockets, wire L2TP up to IPv6.
> Support has been tested with and without hardware offloading.
> 
> Signed-off-by: Benjamin LaHaise <bcrl@kvack.org>

...

> diff --git a/net/l2tp/l2tp_core.c b/net/l2tp/l2tp_core.c
> index f6732b6..8cd5f4b 100644
> --- a/net/l2tp/l2tp_core.c
> +++ b/net/l2tp/l2tp_core.c
> @@ -53,6 +53,9 @@
>  #include <net/inet_common.h>
>  #include <net/xfrm.h>
>  #include <net/protocol.h>
> +#include <net/inet6_connection_sock.h>
> +#include <net/inet_ecn.h>
> +#include <net/ip6_route.h>
>  
>  #include <asm/byteorder.h>
>  #include <linux/atomic.h>
> @@ -446,21 +449,43 @@ static inline int l2tp_verify_udp_checksum(struct sock *sk,
>  {
>  	struct udphdr *uh = udp_hdr(skb);
>  	u16 ulen = ntohs(uh->len);
> -	struct inet_sock *inet;
>  	__wsum psum;
>  
> -	if (sk->sk_no_check || skb_csum_unnecessary(skb) || !uh->check)
> -		return 0;
> -
> -	inet = inet_sk(sk);
> -	psum = csum_tcpudp_nofold(inet->inet_saddr, inet->inet_daddr, ulen,
> -				  IPPROTO_UDP, 0);
> -
> -	if ((skb->ip_summed == CHECKSUM_COMPLETE) &&
> -	    !csum_fold(csum_add(psum, skb->csum)))
> +	if (sk->sk_no_check || skb_csum_unnecessary(skb))
>  		return 0;
>  
> -	skb->csum = psum;
> +#if IS_ENABLED(CONFIG_IPV6)
> +	if (sk->sk_family == PF_INET6) {
> +		if (!uh->check) {
> +			LIMIT_NETDEBUG(KERN_INFO "L2TP: IPv6: checksum is 0\n");
> +			return 1;
> +		}
> +		if ((skb->ip_summed == CHECKSUM_COMPLETE) &&
> +		    !csum_ipv6_magic(&ipv6_hdr(skb)->saddr,
> +				     &ipv6_hdr(skb)->daddr, ulen,
> +				     IPPROTO_UDP, skb->csum)) {
> +			skb->ip_summed = CHECKSUM_UNNECESSARY;
> +			return 0;
> +		}
> +		skb->csum = ~csum_unfold(csum_ipv6_magic(&ipv6_hdr(skb)->saddr,
> +							 &ipv6_hdr(skb)->daddr,
> +							 skb->len, IPPROTO_UDP,
> +							 0));
> +	} else
> +#endif
> +	{
> +		struct inet_sock *inet;
> +		if (!uh->check)
> +			return 0;
> +		inet = inet_sk(sk);
> +		psum = csum_tcpudp_nofold(inet->inet_saddr, inet->inet_daddr,
> +					  ulen, IPPROTO_UDP, 0);
> +
> +		if ((skb->ip_summed == CHECKSUM_COMPLETE) &&
> +		    !csum_fold(csum_add(psum, skb->csum)))
> +			return 0;
> +		skb->csum = psum;
> +	}
>  
>  	return __skb_checksum_complete(skb);
>  }

I'm seeing UDP checksum errors with a loopback test. Regular
(non-loopback) sessions and loopback ipv4 sessions are fine. I retested
your previous patch and verified that it has the same problem. Although
loopback is a weird config, does it indicate a possible problem? Here is
the debug trace:

[  223.260340] lo: hw csum failure
[  223.260718] Pid: 1146, comm: pppd Not tainted 3.4.0-rc2+ #5
[  223.261373] Call Trace:
[  223.261840]  [<c1247b55>] netdev_rx_csum_fault+0x29/0x30
[  223.262467]  [<c12430ac>] __skb_checksum_complete_head+0x42/0x57
[  223.263159]  [<d8a41230>] l2tp_udp_encap_recv+0x13a/0x413 [l2tp_core]
[  223.263892]  [<d8a78c46>] ? pppol2tp_recv+0x11e/0x11e [l2tp_ppp]
[  223.265741]  [<c12cb69f>] ? rcu_read_unlock+0x4d/0x4f
[  223.266902]  [<c12c0028>] ? addrconf_notify+0x5c4/0x7e2
[  223.268066]  [<c12cd2d6>] udpv6_queue_rcv_skb+0x4a/0x256
[  223.269176]  [<c12cd9c6>] __udp6_lib_rcv+0x2c8/0x400
[  223.269733]  [<c12cdb10>] udpv6_rcv+0x12/0x16
[  223.270222]  [<c12bb1d3>] ip6_input_finish+0x1be/0x346
[  223.270793]  [<c12bb015>] ? T.1206+0x4d/0x4d
[  223.271270]  [<c12bb393>] T.1207+0x38/0x3f
[  223.271729]  [<c12bafa1>] ? rcu_read_unlock+0x4f/0x4f
[  223.272334]  [<c12bb79e>] ip6_input+0x17/0x19
[  223.272824]  [<c12bb015>] ? T.1206+0x4d/0x4d
[  223.273302]  [<c12bafc5>] ip6_rcv_finish+0x24/0x27
[  223.276246]  [<c12bb393>] T.1207+0x38/0x3f
[  223.276727]  [<c12bb726>] ipv6_rcv+0x38c/0x3ed
[  223.277242]  [<c12bafa1>] ? rcu_read_unlock+0x4f/0x4f
[  223.277833]  [<c124b97b>] __netif_receive_skb+0x46c/0x4a7
[  223.278459]  [<c124ba54>] process_backlog+0x9e/0x16e
[  223.279068]  [<c124c26d>] net_rx_action+0x9d/0x1af
[  223.279701]  [<c102cadd>] __do_softirq+0xb1/0x17f
[  223.280281]  [<c102ca2c>] ? irq_enter+0x5d/0x5d
[  223.280807]  <IRQ>  [<c122112d>] ? ppp_channel_push+0x62/0x93
[  223.281489]  [<c102c9a0>] ? _local_bh_enable_ip+0x8e/0xa6
[  223.282406]  [<c102c9c0>] ? local_bh_enable_ip+0x8/0xa
[  223.283026]  [<c12fe2d6>] ? _raw_spin_unlock_bh+0x25/0x28
[  223.283655]  [<c122112d>] ? ppp_channel_push+0x62/0x93
[  223.284308]  [<c1221200>] ? ppp_write+0xa2/0xac
[  223.284855]  [<c122115e>] ? ppp_channel_push+0x93/0x93
[  223.285451]  [<c10e0412>] ? vfs_write+0x80/0xde
[  223.286059]  [<c10e0d2a>] ? fget_light+0x2b/0x8f
[  223.286593]  [<c10e0507>] ? sys_write+0x3b/0x60
[  223.287121]  [<c130431f>] ? sysenter_do_call+0x12/0x38
David Miller April 18, 2012, 6:30 p.m. UTC | #2
From: Benjamin LaHaise <bcrl@kvack.org>
Date: Wed, 18 Apr 2012 09:44:38 -0400

> +#if IS_ENABLED(CONFIG_IPV6)
> +	if (sk->sk_family == PF_INET6) {
> +		if (!uh->check) {
> +			LIMIT_NETDEBUG(KERN_INFO "L2TP: IPv6: checksum is 0\n");
> +			return 1;
> +		}
> +		if ((skb->ip_summed == CHECKSUM_COMPLETE) &&
> +		    !csum_ipv6_magic(&ipv6_hdr(skb)->saddr,
> +				     &ipv6_hdr(skb)->daddr, ulen,
> +				     IPPROTO_UDP, skb->csum)) {
> +			skb->ip_summed = CHECKSUM_UNNECESSARY;
> +			return 0;
> +		}
> +		skb->csum = ~csum_unfold(csum_ipv6_magic(&ipv6_hdr(skb)->saddr,
> +							 &ipv6_hdr(skb)->daddr,
> +							 skb->len, IPPROTO_UDP,
> +							 0));

Both the csum_ipv6_magic() calls should either use "ulen" or both
should use "skb->len".
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/include/linux/if_pppol2tp.h b/include/linux/if_pppol2tp.h
index 23cefa1..b477541 100644
--- a/include/linux/if_pppol2tp.h
+++ b/include/linux/if_pppol2tp.h
@@ -19,10 +19,11 @@ 
 
 #ifdef __KERNEL__
 #include <linux/in.h>
+#include <linux/in6.h>
 #endif
 
 /* Structure used to connect() the socket to a particular tunnel UDP
- * socket.
+ * socket over IPv4.
  */
 struct pppol2tp_addr {
 	__kernel_pid_t	pid;		/* pid that owns the fd.
@@ -35,6 +36,20 @@  struct pppol2tp_addr {
 	__u16 d_tunnel, d_session;	/* For sending outgoing packets */
 };
 
+/* Structure used to connect() the socket to a particular tunnel UDP
+ * socket over IPv6.
+ */
+struct pppol2tpin6_addr {
+	__kernel_pid_t	pid;		/* pid that owns the fd.
+					 * 0 => current */
+	int	fd;			/* FD of UDP socket to use */
+
+	__u16 s_tunnel, s_session;	/* For matching incoming packets */
+	__u16 d_tunnel, d_session;	/* For sending outgoing packets */
+
+	struct sockaddr_in6 addr;	/* IP address and port to send to */
+};
+
 /* The L2TPv3 protocol changes tunnel and session ids from 16 to 32
  * bits. So we need a different sockaddr structure.
  */
@@ -49,6 +64,17 @@  struct pppol2tpv3_addr {
 	__u32 d_tunnel, d_session;	/* For sending outgoing packets */
 };
 
+struct pppol2tpv3in6_addr {
+	__kernel_pid_t	pid;		/* pid that owns the fd.
+					 * 0 => current */
+	int	fd;			/* FD of UDP or IP socket to use */
+
+	__u32 s_tunnel, s_session;	/* For matching incoming packets */
+	__u32 d_tunnel, d_session;	/* For sending outgoing packets */
+
+	struct sockaddr_in6 addr;	/* IP address and port to send to */
+};
+
 /* Socket options:
  * DEBUG	- bitmask of debug message categories
  * SENDSEQ	- 0 => don't send packets with sequence numbers
diff --git a/include/linux/if_pppox.h b/include/linux/if_pppox.h
index b5f927f..6720d57 100644
--- a/include/linux/if_pppox.h
+++ b/include/linux/if_pppox.h
@@ -83,6 +83,12 @@  struct sockaddr_pppol2tp {
 	struct pppol2tp_addr pppol2tp;
 } __attribute__((packed));
 
+struct sockaddr_pppol2tpin6 {
+	__kernel_sa_family_t sa_family; /* address family, AF_PPPOX */
+	unsigned int    sa_protocol;    /* protocol identifier */
+	struct pppol2tpin6_addr pppol2tp;
+} __attribute__((packed));
+
 /* The L2TPv3 protocol changes tunnel and session ids from 16 to 32
  * bits. So we need a different sockaddr structure.
  */
@@ -92,6 +98,12 @@  struct sockaddr_pppol2tpv3 {
 	struct pppol2tpv3_addr pppol2tp;
 } __attribute__((packed));
 
+struct sockaddr_pppol2tpv3in6 {
+	__kernel_sa_family_t sa_family; /* address family, AF_PPPOX */
+	unsigned int    sa_protocol;    /* protocol identifier */
+	struct pppol2tpv3in6_addr pppol2tp;
+} __attribute__((packed));
+
 /*********************************************************************
  *
  * ioctl interface for defining forwarding of connections
diff --git a/net/l2tp/l2tp_core.c b/net/l2tp/l2tp_core.c
index f6732b6..8cd5f4b 100644
--- a/net/l2tp/l2tp_core.c
+++ b/net/l2tp/l2tp_core.c
@@ -53,6 +53,9 @@ 
 #include <net/inet_common.h>
 #include <net/xfrm.h>
 #include <net/protocol.h>
+#include <net/inet6_connection_sock.h>
+#include <net/inet_ecn.h>
+#include <net/ip6_route.h>
 
 #include <asm/byteorder.h>
 #include <linux/atomic.h>
@@ -446,21 +449,43 @@  static inline int l2tp_verify_udp_checksum(struct sock *sk,
 {
 	struct udphdr *uh = udp_hdr(skb);
 	u16 ulen = ntohs(uh->len);
-	struct inet_sock *inet;
 	__wsum psum;
 
-	if (sk->sk_no_check || skb_csum_unnecessary(skb) || !uh->check)
-		return 0;
-
-	inet = inet_sk(sk);
-	psum = csum_tcpudp_nofold(inet->inet_saddr, inet->inet_daddr, ulen,
-				  IPPROTO_UDP, 0);
-
-	if ((skb->ip_summed == CHECKSUM_COMPLETE) &&
-	    !csum_fold(csum_add(psum, skb->csum)))
+	if (sk->sk_no_check || skb_csum_unnecessary(skb))
 		return 0;
 
-	skb->csum = psum;
+#if IS_ENABLED(CONFIG_IPV6)
+	if (sk->sk_family == PF_INET6) {
+		if (!uh->check) {
+			LIMIT_NETDEBUG(KERN_INFO "L2TP: IPv6: checksum is 0\n");
+			return 1;
+		}
+		if ((skb->ip_summed == CHECKSUM_COMPLETE) &&
+		    !csum_ipv6_magic(&ipv6_hdr(skb)->saddr,
+				     &ipv6_hdr(skb)->daddr, ulen,
+				     IPPROTO_UDP, skb->csum)) {
+			skb->ip_summed = CHECKSUM_UNNECESSARY;
+			return 0;
+		}
+		skb->csum = ~csum_unfold(csum_ipv6_magic(&ipv6_hdr(skb)->saddr,
+							 &ipv6_hdr(skb)->daddr,
+							 skb->len, IPPROTO_UDP,
+							 0));
+	} else
+#endif
+	{
+		struct inet_sock *inet;
+		if (!uh->check)
+			return 0;
+		inet = inet_sk(sk);
+		psum = csum_tcpudp_nofold(inet->inet_saddr, inet->inet_daddr,
+					  ulen, IPPROTO_UDP, 0);
+
+		if ((skb->ip_summed == CHECKSUM_COMPLETE) &&
+		    !csum_fold(csum_add(psum, skb->csum)))
+			return 0;
+		skb->csum = psum;
+	}
 
 	return __skb_checksum_complete(skb);
 }
@@ -988,7 +1013,12 @@  static int l2tp_xmit_core(struct l2tp_session *session, struct sk_buff *skb,
 
 	/* Queue the packet to IP for output */
 	skb->local_df = 1;
-	error = ip_queue_xmit(skb, fl);
+#if IS_ENABLED(CONFIG_IPV6)
+	if (skb->sk->sk_family == PF_INET6)
+		error = inet6_csk_xmit(skb, NULL);
+	else
+#endif
+		error = ip_queue_xmit(skb, fl);
 
 	/* Update stats */
 	if (error >= 0) {
@@ -1021,6 +1051,31 @@  static inline void l2tp_skb_set_owner_w(struct sk_buff *skb, struct sock *sk)
 	skb->destructor = l2tp_sock_wfree;
 }
 
+#if IS_ENABLED(CONFIG_IPV6)
+static void l2tp_xmit_ipv6_csum(struct sock *sk, struct sk_buff *skb,
+				int udp_len)
+{
+	struct ipv6_pinfo *np = inet6_sk(sk);
+	struct udphdr *uh = udp_hdr(skb);
+
+	if (!skb_dst(skb) || !skb_dst(skb)->dev ||
+	    !(skb_dst(skb)->dev->features & NETIF_F_IPV6_CSUM)) {
+		skb->ip_summed = CHECKSUM_COMPLETE;
+		skb->csum = skb_checksum(skb, 0, udp_len, 0);
+		uh->check = csum_ipv6_magic(&np->saddr, &np->daddr, udp_len,
+					    IPPROTO_UDP, skb->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_ipv6_magic(&np->saddr, &np->daddr,
+					     udp_len, IPPROTO_UDP, 0);
+	}
+}
+#endif
+
 /* If caller requires the skb to have a ppp header, the header must be
  * inserted in the skb data before calling this function.
  */
@@ -1089,6 +1144,11 @@  int l2tp_xmit_skb(struct l2tp_session *session, struct sk_buff *skb, int hdr_len
 		uh->check = 0;
 
 		/* Calculate UDP checksum if configured to do so */
+#if IS_ENABLED(CONFIG_IPV6)
+		if (sk->sk_family == PF_INET6)
+			l2tp_xmit_ipv6_csum(sk, skb, udp_len);
+		else
+#endif
 		if (sk->sk_no_check == UDP_CSUM_NOXMIT)
 			skb->ip_summed = CHECKSUM_NONE;
 		else if ((skb_dst(skb) && skb_dst(skb)->dev) &&
@@ -1424,6 +1484,11 @@  int l2tp_tunnel_create(struct net *net, int fd, int version, u32 tunnel_id, u32
 		/* 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;
+#if IS_ENABLED(CONFIG_IPV6)
+		if (sk->sk_family == PF_INET6)
+			udpv6_encap_enable();
+		else
+#endif
 		udp_encap_enable();
 	}
 
diff --git a/net/l2tp/l2tp_ppp.c b/net/l2tp/l2tp_ppp.c
index 1addd9f..27b9dec 100644
--- a/net/l2tp/l2tp_ppp.c
+++ b/net/l2tp/l2tp_ppp.c
@@ -916,7 +916,7 @@  static int pppol2tp_getname(struct socket *sock, struct sockaddr *uaddr,
 	}
 
 	inet = inet_sk(tunnel->sock);
-	if (tunnel->version == 2) {
+	if ((tunnel->version == 2) && (tunnel->sock->sk_family == AF_INET)) {
 		struct sockaddr_pppol2tp sp;
 		len = sizeof(sp);
 		memset(&sp, 0, len);
@@ -932,6 +932,46 @@  static int pppol2tp_getname(struct socket *sock, struct sockaddr *uaddr,
 		sp.pppol2tp.addr.sin_port = inet->inet_dport;
 		sp.pppol2tp.addr.sin_addr.s_addr = inet->inet_daddr;
 		memcpy(uaddr, &sp, len);
+#if IS_ENABLED(CONFIG_IPV6)
+	} else if ((tunnel->version == 2) &&
+		   (tunnel->sock->sk_family == AF_INET6)) {
+		struct ipv6_pinfo *np = inet6_sk(tunnel->sock);
+		struct sockaddr_pppol2tpin6 sp;
+		len = sizeof(sp);
+		memset(&sp, 0, len);
+		sp.sa_family	= AF_PPPOX;
+		sp.sa_protocol	= PX_PROTO_OL2TP;
+		sp.pppol2tp.fd  = tunnel->fd;
+		sp.pppol2tp.pid = pls->owner;
+		sp.pppol2tp.s_tunnel = tunnel->tunnel_id;
+		sp.pppol2tp.d_tunnel = tunnel->peer_tunnel_id;
+		sp.pppol2tp.s_session = session->session_id;
+		sp.pppol2tp.d_session = session->peer_session_id;
+		sp.pppol2tp.addr.sin6_family = AF_INET6;
+		sp.pppol2tp.addr.sin6_port = inet->inet_dport;
+		memcpy(&sp.pppol2tp.addr.sin6_addr, &np->daddr,
+		       sizeof(np->daddr));
+		memcpy(uaddr, &sp, len);
+	} else if ((tunnel->version == 3) &&
+		   (tunnel->sock->sk_family == AF_INET6)) {
+		struct ipv6_pinfo *np = inet6_sk(tunnel->sock);
+		struct sockaddr_pppol2tpv3in6 sp;
+		len = sizeof(sp);
+		memset(&sp, 0, len);
+		sp.sa_family	= AF_PPPOX;
+		sp.sa_protocol	= PX_PROTO_OL2TP;
+		sp.pppol2tp.fd  = tunnel->fd;
+		sp.pppol2tp.pid = pls->owner;
+		sp.pppol2tp.s_tunnel = tunnel->tunnel_id;
+		sp.pppol2tp.d_tunnel = tunnel->peer_tunnel_id;
+		sp.pppol2tp.s_session = session->session_id;
+		sp.pppol2tp.d_session = session->peer_session_id;
+		sp.pppol2tp.addr.sin6_family = AF_INET6;
+		sp.pppol2tp.addr.sin6_port = inet->inet_dport;
+		memcpy(&sp.pppol2tp.addr.sin6_addr, &np->daddr,
+		       sizeof(np->daddr));
+		memcpy(uaddr, &sp, len);
+#endif
 	} else if (tunnel->version == 3) {
 		struct sockaddr_pppol2tpv3 sp;
 		len = sizeof(sp);