diff mbox

[v2,11/12] l2tp: Add support for static unmanaged L2TPv3 tunnels

Message ID 20100329095737.30460.74520.stgit@bert.katalix.com
State Changes Requested, archived
Delegated to: David Miller
Headers show

Commit Message

James Chapman March 29, 2010, 9:57 a.m. UTC
This patch adds support for static (unmanaged) L2TPv3 tunnels, where
the tunnel socket is created by the kernel rather than being created
by userspace. This means L2TP tunnels and sessions can be created
manually, without needing an L2TP control protocol implemented in
userspace. This might be useful where the user wants a simple ethernet
over IP tunnel.

A patch to iproute2 adds a new command set under "ip l2tp" to make use
of this feature. This will be submitted separately.

Signed-off-by: James Chapman <jchapman@katalix.com>
Reviewed-by: Randy Dunlap <randy.dunlap@oracle.com>
---
 net/l2tp/l2tp_core.c    |  111 +++++++++++++++++++++++++++++++++++++++++++----
 net/l2tp/l2tp_core.h    |    7 +++
 net/l2tp/l2tp_netlink.c |   18 ++++++--
 3 files changed, 122 insertions(+), 14 deletions(-)


--
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

Comments

Eric Dumazet March 29, 2010, 10:40 a.m. UTC | #1
Le lundi 29 mars 2010 à 10:57 +0100, James Chapman a écrit :
> This patch adds support for static (unmanaged) L2TPv3 tunnels, where
> the tunnel socket is created by the kernel rather than being created
> by userspace. This means L2TP tunnels and sessions can be created
> manually, without needing an L2TP control protocol implemented in
> userspace. This might be useful where the user wants a simple ethernet
> over IP tunnel.
> 
> A patch to iproute2 adds a new command set under "ip l2tp" to make use
> of this feature. This will be submitted separately.
> 
> Signed-off-by: James Chapman <jchapman@katalix.com>
> Reviewed-by: Randy Dunlap <randy.dunlap@oracle.com>
> ---
>  net/l2tp/l2tp_core.c    |  111 +++++++++++++++++++++++++++++++++++++++++++----
>  net/l2tp/l2tp_core.h    |    7 +++
>  net/l2tp/l2tp_netlink.c |   18 ++++++--
>  3 files changed, 122 insertions(+), 14 deletions(-)
> 
> diff --git a/net/l2tp/l2tp_core.c b/net/l2tp/l2tp_core.c
> index c08c859..1431b39 100644
> --- a/net/l2tp/l2tp_core.c
> +++ b/net/l2tp/l2tp_core.c
> @@ -1259,6 +1259,78 @@ void l2tp_tunnel_free(struct l2tp_tunnel *tunnel)
>  }
>  EXPORT_SYMBOL_GPL(l2tp_tunnel_free);
>  
> +/* Create a socket for the tunnel, if one isn't set up by
> + * userspace. This is used for static tunnels where there is no
> + * managing L2TP daemon.
> + */
> +static int l2tp_tunnel_sock_create(u32 tunnel_id, u32 peer_tunnel_id, struct l2tp_tunnel_cfg *cfg, struct socket **sockp)
> +{
> +	int err = -EINVAL;
> +	struct sockaddr_in udp_addr;
> +	struct sockaddr_l2tpip ip_addr;
> +	struct socket *sock;
> +
> +	switch (cfg->encap) {
> +	case L2TP_ENCAPTYPE_UDP:
> +		err = sock_create(AF_INET, SOCK_DGRAM, 0, sockp);
> +		if (err < 0)
> +			goto out;
> +
> +		sock = *sockp;
> +
> +		memset(&udp_addr, 0, sizeof(udp_addr));
> +		udp_addr.sin_family = AF_INET;
> +		udp_addr.sin_addr = cfg->local_ip;
> +		udp_addr.sin_port = htons(cfg->local_udp_port);
> +		err = kernel_bind(sock, (struct sockaddr *) &udp_addr, sizeof(udp_addr));
> +		if (err < 0)
> +			goto out;
> +

I cant see how you use cfg->use_udp_checksums, something like : ?

	if (!cfg->use_udp_checksums)
		sk->sk_no_check = UDP_CSUM_NOXMIT;

> +		udp_addr.sin_family = AF_INET;
> +		udp_addr.sin_addr = cfg->peer_ip;
> +		udp_addr.sin_port = htons(cfg->peer_udp_port);
> +		err = kernel_connect(sock, (struct sockaddr *) &udp_addr, sizeof(udp_addr), 0);
> +		if (err < 0)
> +			goto out;
> +
> +		break;
> +	case L2TP_ENCAPTYPE_IP:
> +		err = sock_create(AF_INET, SOCK_DGRAM, IPPROTO_L2TP, sockp);
> +		if (err < 0)
> +			goto out;
> +
> +		sock = *sockp;
> +
> +		memset(&ip_addr, 0, sizeof(ip_addr));
> +		ip_addr.l2tp_family = AF_INET;
> +		ip_addr.l2tp_addr = cfg->local_ip;
> +		ip_addr.l2tp_conn_id = tunnel_id;
> +		err = kernel_bind(sock, (struct sockaddr *) &ip_addr, sizeof(ip_addr));
> +		if (err < 0)
> +			goto out;
> +
> +		ip_addr.l2tp_family = AF_INET;
> +		ip_addr.l2tp_addr = cfg->peer_ip;
> +		ip_addr.l2tp_conn_id = peer_tunnel_id;
> +		err = kernel_connect(sock, (struct sockaddr *) &ip_addr, sizeof(ip_addr), 0);
> +		if (err < 0)
> +			goto out;
> +
> +		break;
> +
> +	default:
> +		goto out;
> +	}
> +
> +out:
> +	if ((err < 0) && sock) {
> +		sock_release(sock);
> +		*sockp = NULL;
> +	}
> +
> +	return err;
> +}
> +
>  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)
>  {
>  	struct l2tp_tunnel *tunnel = NULL;
> @@ -1269,14 +1341,21 @@ int l2tp_tunnel_create(struct net *net, int fd, int version, u32 tunnel_id, u32
>  	enum l2tp_encap_type encap = L2TP_ENCAPTYPE_UDP;
>  
>  	/* Get the tunnel socket from the fd, which was opened by
> -	 * the userspace L2TP daemon.
> +	 * the userspace L2TP daemon. If not specified, create a
> +	 * kernel socket.
>  	 */
> -	err = -EBADF;
> -	sock = sockfd_lookup(fd, &err);
> -	if (!sock) {
> -		printk(KERN_ERR "tunl %hu: sockfd_lookup(fd=%d) returned %d\n",
> -		       tunnel_id, fd, err);
> -		goto err;
> +	if (fd < 0) {
> +		err = l2tp_tunnel_sock_create(tunnel_id, peer_tunnel_id, cfg, &sock);
> +		if (err < 0)
> +			goto err;
> +	} else {
> +		err = -EBADF;
> +		sock = sockfd_lookup(fd, &err);
> +		if (!sock) {
> +			printk(KERN_ERR "tunl %hu: sockfd_lookup(fd=%d) returned %d\n",
> +			       tunnel_id, fd, err);
> +			goto err;
> +		}
>  	}
>  
>  	sk = sock->sk;
> @@ -1369,7 +1448,10 @@ err:
>  	if (tunnelp)
>  		*tunnelp = tunnel;
>  
> -	if (sock)
> +	/* If tunnel's socket was created by the kernel, it doesn't
> +	 *  have a file.
> +	 */
> +	if (sock && sock->file)
>  		sockfd_put(sock);
>  
>  	return err;
> @@ -1381,13 +1463,22 @@ EXPORT_SYMBOL_GPL(l2tp_tunnel_create);
>  int l2tp_tunnel_delete(struct l2tp_tunnel *tunnel)
>  {
>  	int err = 0;
> +	struct socket *sock = tunnel->sock ? tunnel->sock->sk_socket : NULL;
>  
>  	/* Force the tunnel socket to close. This will eventually
>  	 * cause the tunnel to be deleted via the normal socket close
>  	 * mechanisms when userspace closes the tunnel socket.
>  	 */
> -	if ((tunnel->sock != NULL) && (tunnel->sock->sk_socket != NULL))
> -		err = inet_shutdown(tunnel->sock->sk_socket, 2);
> +	if (sock != NULL) {
> +		err = inet_shutdown(sock, 2);
> +
> +		/* If the tunnel's socket was created by the kernel,
> +		 * close the socket here since the socket was not
> +		 * created by userspace.
> +		 */
> +		if (sock->file == NULL)
> +			err = inet_release(sock);
> +	}
>  
>  	return err;
>  }
> diff --git a/net/l2tp/l2tp_core.h b/net/l2tp/l2tp_core.h
> index c5f2cc9..7e3387b 100644
> --- a/net/l2tp/l2tp_core.h
> +++ b/net/l2tp/l2tp_core.h
> @@ -146,6 +146,13 @@ 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;
> +	u16			local_udp_port;
> +	u16			peer_udp_port;
> +	int			use_udp_checksums:1;
>  };
>  
>  struct l2tp_tunnel {
> diff --git a/net/l2tp/l2tp_netlink.c b/net/l2tp/l2tp_netlink.c
> index e051c13..115ea01 100644
> --- a/net/l2tp/l2tp_netlink.c
> +++ b/net/l2tp/l2tp_netlink.c
> @@ -129,11 +129,21 @@ static int l2tp_nl_cmd_tunnel_create(struct sk_buff *skb, struct genl_info *info
>  	}
>  	cfg.encap = nla_get_u16(info->attrs[L2TP_ATTR_ENCAP_TYPE]);
>  
> -	if (!info->attrs[L2TP_ATTR_FD]) {
> -		ret = -EINVAL;
> -		goto out;
> +	fd = -1;
> +	if (info->attrs[L2TP_ATTR_FD]) {
> +		fd = nla_get_u32(info->attrs[L2TP_ATTR_FD]);
> +	} else {
> +		if (info->attrs[L2TP_ATTR_IP_SADDR])
> +			cfg.local_ip.s_addr = nla_get_be32(info->attrs[L2TP_ATTR_IP_SADDR]);
> +		if (info->attrs[L2TP_ATTR_IP_DADDR])
> +			cfg.peer_ip.s_addr = nla_get_be32(info->attrs[L2TP_ATTR_IP_DADDR]);
> +		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]);
>  	}
> -	fd = nla_get_u32(info->attrs[L2TP_ATTR_FD]);
>  
>  	if (info->attrs[L2TP_ATTR_DEBUG])
>  		cfg.debug = nla_get_u32(info->attrs[L2TP_ATTR_DEBUG]);
> 


--
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
James Chapman March 29, 2010, 10:52 a.m. UTC | #2
Eric Dumazet wrote:
> Le lundi 29 mars 2010 à 10:57 +0100, James Chapman a écrit :
>> This patch adds support for static (unmanaged) L2TPv3 tunnels, where
>> the tunnel socket is created by the kernel rather than being created
>> by userspace. This means L2TP tunnels and sessions can be created
>> manually, without needing an L2TP control protocol implemented in
>> userspace. This might be useful where the user wants a simple ethernet
>> over IP tunnel.
>>
>> A patch to iproute2 adds a new command set under "ip l2tp" to make use
>> of this feature. This will be submitted separately.
>>
>> Signed-off-by: James Chapman <jchapman@katalix.com>
>> Reviewed-by: Randy Dunlap <randy.dunlap@oracle.com>
>> ---
>>  net/l2tp/l2tp_core.c    |  111 +++++++++++++++++++++++++++++++++++++++++++----
>>  net/l2tp/l2tp_core.h    |    7 +++
>>  net/l2tp/l2tp_netlink.c |   18 ++++++--
>>  3 files changed, 122 insertions(+), 14 deletions(-)
>>
>> diff --git a/net/l2tp/l2tp_core.c b/net/l2tp/l2tp_core.c
>> index c08c859..1431b39 100644
>> --- a/net/l2tp/l2tp_core.c
>> +++ b/net/l2tp/l2tp_core.c
>> @@ -1259,6 +1259,78 @@ void l2tp_tunnel_free(struct l2tp_tunnel *tunnel)
>>  }
>>  EXPORT_SYMBOL_GPL(l2tp_tunnel_free);
>>  
>> +/* Create a socket for the tunnel, if one isn't set up by
>> + * userspace. This is used for static tunnels where there is no
>> + * managing L2TP daemon.
>> + */
>> +static int l2tp_tunnel_sock_create(u32 tunnel_id, u32 peer_tunnel_id, struct l2tp_tunnel_cfg *cfg, struct socket **sockp)
>> +{
>> +	int err = -EINVAL;
>> +	struct sockaddr_in udp_addr;
>> +	struct sockaddr_l2tpip ip_addr;
>> +	struct socket *sock;
>> +
>> +	switch (cfg->encap) {
>> +	case L2TP_ENCAPTYPE_UDP:
>> +		err = sock_create(AF_INET, SOCK_DGRAM, 0, sockp);
>> +		if (err < 0)
>> +			goto out;
>> +
>> +		sock = *sockp;
>> +
>> +		memset(&udp_addr, 0, sizeof(udp_addr));
>> +		udp_addr.sin_family = AF_INET;
>> +		udp_addr.sin_addr = cfg->local_ip;
>> +		udp_addr.sin_port = htons(cfg->local_udp_port);
>> +		err = kernel_bind(sock, (struct sockaddr *) &udp_addr, sizeof(udp_addr));
>> +		if (err < 0)
>> +			goto out;
>> +
> 
> I cant see how you use cfg->use_udp_checksums, something like : ?
> 
> 	if (!cfg->use_udp_checksums)
> 		sk->sk_no_check = UDP_CSUM_NOXMIT;

Good point. I forgot to add control of UDP checksums for the kernel
socket case. Thanks!

> 
>> +		udp_addr.sin_family = AF_INET;
>> +		udp_addr.sin_addr = cfg->peer_ip;
>> +		udp_addr.sin_port = htons(cfg->peer_udp_port);
>> +		err = kernel_connect(sock, (struct sockaddr *) &udp_addr, sizeof(udp_addr), 0);
>> +		if (err < 0)
>> +			goto out;
>> +
>> +		break;
>> +	case L2TP_ENCAPTYPE_IP:
>> +		err = sock_create(AF_INET, SOCK_DGRAM, IPPROTO_L2TP, sockp);
>> +		if (err < 0)
>> +			goto out;
>> +
>> +		sock = *sockp;
>> +
>> +		memset(&ip_addr, 0, sizeof(ip_addr));
>> +		ip_addr.l2tp_family = AF_INET;
>> +		ip_addr.l2tp_addr = cfg->local_ip;
>> +		ip_addr.l2tp_conn_id = tunnel_id;
>> +		err = kernel_bind(sock, (struct sockaddr *) &ip_addr, sizeof(ip_addr));
>> +		if (err < 0)
>> +			goto out;
>> +
>> +		ip_addr.l2tp_family = AF_INET;
>> +		ip_addr.l2tp_addr = cfg->peer_ip;
>> +		ip_addr.l2tp_conn_id = peer_tunnel_id;
>> +		err = kernel_connect(sock, (struct sockaddr *) &ip_addr, sizeof(ip_addr), 0);
>> +		if (err < 0)
>> +			goto out;
>> +
>> +		break;
>> +
>> +	default:
>> +		goto out;
>> +	}
>> +
>> +out:
>> +	if ((err < 0) && sock) {
>> +		sock_release(sock);
>> +		*sockp = NULL;
>> +	}
>> +
>> +	return err;
>> +}
>> +
>>  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)
>>  {
>>  	struct l2tp_tunnel *tunnel = NULL;
>> @@ -1269,14 +1341,21 @@ int l2tp_tunnel_create(struct net *net, int fd, int version, u32 tunnel_id, u32
>>  	enum l2tp_encap_type encap = L2TP_ENCAPTYPE_UDP;
>>  
>>  	/* Get the tunnel socket from the fd, which was opened by
>> -	 * the userspace L2TP daemon.
>> +	 * the userspace L2TP daemon. If not specified, create a
>> +	 * kernel socket.
>>  	 */
>> -	err = -EBADF;
>> -	sock = sockfd_lookup(fd, &err);
>> -	if (!sock) {
>> -		printk(KERN_ERR "tunl %hu: sockfd_lookup(fd=%d) returned %d\n",
>> -		       tunnel_id, fd, err);
>> -		goto err;
>> +	if (fd < 0) {
>> +		err = l2tp_tunnel_sock_create(tunnel_id, peer_tunnel_id, cfg, &sock);
>> +		if (err < 0)
>> +			goto err;
>> +	} else {
>> +		err = -EBADF;
>> +		sock = sockfd_lookup(fd, &err);
>> +		if (!sock) {
>> +			printk(KERN_ERR "tunl %hu: sockfd_lookup(fd=%d) returned %d\n",
>> +			       tunnel_id, fd, err);
>> +			goto err;
>> +		}
>>  	}
>>  
>>  	sk = sock->sk;
>> @@ -1369,7 +1448,10 @@ err:
>>  	if (tunnelp)
>>  		*tunnelp = tunnel;
>>  
>> -	if (sock)
>> +	/* If tunnel's socket was created by the kernel, it doesn't
>> +	 *  have a file.
>> +	 */
>> +	if (sock && sock->file)
>>  		sockfd_put(sock);
>>  
>>  	return err;
>> @@ -1381,13 +1463,22 @@ EXPORT_SYMBOL_GPL(l2tp_tunnel_create);
>>  int l2tp_tunnel_delete(struct l2tp_tunnel *tunnel)
>>  {
>>  	int err = 0;
>> +	struct socket *sock = tunnel->sock ? tunnel->sock->sk_socket : NULL;
>>  
>>  	/* Force the tunnel socket to close. This will eventually
>>  	 * cause the tunnel to be deleted via the normal socket close
>>  	 * mechanisms when userspace closes the tunnel socket.
>>  	 */
>> -	if ((tunnel->sock != NULL) && (tunnel->sock->sk_socket != NULL))
>> -		err = inet_shutdown(tunnel->sock->sk_socket, 2);
>> +	if (sock != NULL) {
>> +		err = inet_shutdown(sock, 2);
>> +
>> +		/* If the tunnel's socket was created by the kernel,
>> +		 * close the socket here since the socket was not
>> +		 * created by userspace.
>> +		 */
>> +		if (sock->file == NULL)
>> +			err = inet_release(sock);
>> +	}
>>  
>>  	return err;
>>  }
>> diff --git a/net/l2tp/l2tp_core.h b/net/l2tp/l2tp_core.h
>> index c5f2cc9..7e3387b 100644
>> --- a/net/l2tp/l2tp_core.h
>> +++ b/net/l2tp/l2tp_core.h
>> @@ -146,6 +146,13 @@ 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;
>> +	u16			local_udp_port;
>> +	u16			peer_udp_port;
>> +	int			use_udp_checksums:1;
>>  };
>>  
>>  struct l2tp_tunnel {
>> diff --git a/net/l2tp/l2tp_netlink.c b/net/l2tp/l2tp_netlink.c
>> index e051c13..115ea01 100644
>> --- a/net/l2tp/l2tp_netlink.c
>> +++ b/net/l2tp/l2tp_netlink.c
>> @@ -129,11 +129,21 @@ static int l2tp_nl_cmd_tunnel_create(struct sk_buff *skb, struct genl_info *info
>>  	}
>>  	cfg.encap = nla_get_u16(info->attrs[L2TP_ATTR_ENCAP_TYPE]);
>>  
>> -	if (!info->attrs[L2TP_ATTR_FD]) {
>> -		ret = -EINVAL;
>> -		goto out;
>> +	fd = -1;
>> +	if (info->attrs[L2TP_ATTR_FD]) {
>> +		fd = nla_get_u32(info->attrs[L2TP_ATTR_FD]);
>> +	} else {
>> +		if (info->attrs[L2TP_ATTR_IP_SADDR])
>> +			cfg.local_ip.s_addr = nla_get_be32(info->attrs[L2TP_ATTR_IP_SADDR]);
>> +		if (info->attrs[L2TP_ATTR_IP_DADDR])
>> +			cfg.peer_ip.s_addr = nla_get_be32(info->attrs[L2TP_ATTR_IP_DADDR]);
>> +		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]);
>>  	}
>> -	fd = nla_get_u32(info->attrs[L2TP_ATTR_FD]);
>>  
>>  	if (info->attrs[L2TP_ATTR_DEBUG])
>>  		cfg.debug = nla_get_u32(info->attrs[L2TP_ATTR_DEBUG]);
>>
> 
> 
>
diff mbox

Patch

diff --git a/net/l2tp/l2tp_core.c b/net/l2tp/l2tp_core.c
index c08c859..1431b39 100644
--- a/net/l2tp/l2tp_core.c
+++ b/net/l2tp/l2tp_core.c
@@ -1259,6 +1259,78 @@  void l2tp_tunnel_free(struct l2tp_tunnel *tunnel)
 }
 EXPORT_SYMBOL_GPL(l2tp_tunnel_free);
 
+/* Create a socket for the tunnel, if one isn't set up by
+ * userspace. This is used for static tunnels where there is no
+ * managing L2TP daemon.
+ */
+static int l2tp_tunnel_sock_create(u32 tunnel_id, u32 peer_tunnel_id, struct l2tp_tunnel_cfg *cfg, struct socket **sockp)
+{
+	int err = -EINVAL;
+	struct sockaddr_in udp_addr;
+	struct sockaddr_l2tpip ip_addr;
+	struct socket *sock;
+
+	switch (cfg->encap) {
+	case L2TP_ENCAPTYPE_UDP:
+		err = sock_create(AF_INET, SOCK_DGRAM, 0, sockp);
+		if (err < 0)
+			goto out;
+
+		sock = *sockp;
+
+		memset(&udp_addr, 0, sizeof(udp_addr));
+		udp_addr.sin_family = AF_INET;
+		udp_addr.sin_addr = cfg->local_ip;
+		udp_addr.sin_port = htons(cfg->local_udp_port);
+		err = kernel_bind(sock, (struct sockaddr *) &udp_addr, sizeof(udp_addr));
+		if (err < 0)
+			goto out;
+
+		udp_addr.sin_family = AF_INET;
+		udp_addr.sin_addr = cfg->peer_ip;
+		udp_addr.sin_port = htons(cfg->peer_udp_port);
+		err = kernel_connect(sock, (struct sockaddr *) &udp_addr, sizeof(udp_addr), 0);
+		if (err < 0)
+			goto out;
+
+		break;
+	case L2TP_ENCAPTYPE_IP:
+		err = sock_create(AF_INET, SOCK_DGRAM, IPPROTO_L2TP, sockp);
+		if (err < 0)
+			goto out;
+
+		sock = *sockp;
+
+		memset(&ip_addr, 0, sizeof(ip_addr));
+		ip_addr.l2tp_family = AF_INET;
+		ip_addr.l2tp_addr = cfg->local_ip;
+		ip_addr.l2tp_conn_id = tunnel_id;
+		err = kernel_bind(sock, (struct sockaddr *) &ip_addr, sizeof(ip_addr));
+		if (err < 0)
+			goto out;
+
+		ip_addr.l2tp_family = AF_INET;
+		ip_addr.l2tp_addr = cfg->peer_ip;
+		ip_addr.l2tp_conn_id = peer_tunnel_id;
+		err = kernel_connect(sock, (struct sockaddr *) &ip_addr, sizeof(ip_addr), 0);
+		if (err < 0)
+			goto out;
+
+		break;
+
+	default:
+		goto out;
+	}
+
+out:
+	if ((err < 0) && sock) {
+		sock_release(sock);
+		*sockp = NULL;
+	}
+
+	return err;
+}
+
 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)
 {
 	struct l2tp_tunnel *tunnel = NULL;
@@ -1269,14 +1341,21 @@  int l2tp_tunnel_create(struct net *net, int fd, int version, u32 tunnel_id, u32
 	enum l2tp_encap_type encap = L2TP_ENCAPTYPE_UDP;
 
 	/* Get the tunnel socket from the fd, which was opened by
-	 * the userspace L2TP daemon.
+	 * the userspace L2TP daemon. If not specified, create a
+	 * kernel socket.
 	 */
-	err = -EBADF;
-	sock = sockfd_lookup(fd, &err);
-	if (!sock) {
-		printk(KERN_ERR "tunl %hu: sockfd_lookup(fd=%d) returned %d\n",
-		       tunnel_id, fd, err);
-		goto err;
+	if (fd < 0) {
+		err = l2tp_tunnel_sock_create(tunnel_id, peer_tunnel_id, cfg, &sock);
+		if (err < 0)
+			goto err;
+	} else {
+		err = -EBADF;
+		sock = sockfd_lookup(fd, &err);
+		if (!sock) {
+			printk(KERN_ERR "tunl %hu: sockfd_lookup(fd=%d) returned %d\n",
+			       tunnel_id, fd, err);
+			goto err;
+		}
 	}
 
 	sk = sock->sk;
@@ -1369,7 +1448,10 @@  err:
 	if (tunnelp)
 		*tunnelp = tunnel;
 
-	if (sock)
+	/* If tunnel's socket was created by the kernel, it doesn't
+	 *  have a file.
+	 */
+	if (sock && sock->file)
 		sockfd_put(sock);
 
 	return err;
@@ -1381,13 +1463,22 @@  EXPORT_SYMBOL_GPL(l2tp_tunnel_create);
 int l2tp_tunnel_delete(struct l2tp_tunnel *tunnel)
 {
 	int err = 0;
+	struct socket *sock = tunnel->sock ? tunnel->sock->sk_socket : NULL;
 
 	/* Force the tunnel socket to close. This will eventually
 	 * cause the tunnel to be deleted via the normal socket close
 	 * mechanisms when userspace closes the tunnel socket.
 	 */
-	if ((tunnel->sock != NULL) && (tunnel->sock->sk_socket != NULL))
-		err = inet_shutdown(tunnel->sock->sk_socket, 2);
+	if (sock != NULL) {
+		err = inet_shutdown(sock, 2);
+
+		/* If the tunnel's socket was created by the kernel,
+		 * close the socket here since the socket was not
+		 * created by userspace.
+		 */
+		if (sock->file == NULL)
+			err = inet_release(sock);
+	}
 
 	return err;
 }
diff --git a/net/l2tp/l2tp_core.h b/net/l2tp/l2tp_core.h
index c5f2cc9..7e3387b 100644
--- a/net/l2tp/l2tp_core.h
+++ b/net/l2tp/l2tp_core.h
@@ -146,6 +146,13 @@  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;
+	u16			local_udp_port;
+	u16			peer_udp_port;
+	int			use_udp_checksums:1;
 };
 
 struct l2tp_tunnel {
diff --git a/net/l2tp/l2tp_netlink.c b/net/l2tp/l2tp_netlink.c
index e051c13..115ea01 100644
--- a/net/l2tp/l2tp_netlink.c
+++ b/net/l2tp/l2tp_netlink.c
@@ -129,11 +129,21 @@  static int l2tp_nl_cmd_tunnel_create(struct sk_buff *skb, struct genl_info *info
 	}
 	cfg.encap = nla_get_u16(info->attrs[L2TP_ATTR_ENCAP_TYPE]);
 
-	if (!info->attrs[L2TP_ATTR_FD]) {
-		ret = -EINVAL;
-		goto out;
+	fd = -1;
+	if (info->attrs[L2TP_ATTR_FD]) {
+		fd = nla_get_u32(info->attrs[L2TP_ATTR_FD]);
+	} else {
+		if (info->attrs[L2TP_ATTR_IP_SADDR])
+			cfg.local_ip.s_addr = nla_get_be32(info->attrs[L2TP_ATTR_IP_SADDR]);
+		if (info->attrs[L2TP_ATTR_IP_DADDR])
+			cfg.peer_ip.s_addr = nla_get_be32(info->attrs[L2TP_ATTR_IP_DADDR]);
+		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]);
 	}
-	fd = nla_get_u32(info->attrs[L2TP_ATTR_FD]);
 
 	if (info->attrs[L2TP_ATTR_DEBUG])
 		cfg.debug = nla_get_u32(info->attrs[L2TP_ATTR_DEBUG]);