diff options
-rw-r--r-- | net/l2tp/l2tp_core.c | 115 | ||||
-rw-r--r-- | net/l2tp/l2tp_core.h | 7 | ||||
-rw-r--r-- | net/l2tp/l2tp_netlink.c | 18 |
3 files changed, 126 insertions, 14 deletions
diff --git a/net/l2tp/l2tp_core.c b/net/l2tp/l2tp_core.c index 473cf2d63905..13ed85baf4e9 100644 --- a/net/l2tp/l2tp_core.c +++ b/net/l2tp/l2tp_core.c @@ -1218,6 +1218,82 @@ 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; + + if (!cfg->use_udp_checksums) + sock->sk->sk_no_check = UDP_CSUM_NOXMIT; + + 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; @@ -1228,14 +1304,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; @@ -1329,7 +1412,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; @@ -1341,13 +1427,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 571335530c6f..a961c77e0867 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 3d0f7f6f7488..12341a6cc70e 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]); |