#include <linux/module.h>
#include <linux/string.h>
#include <linux/list.h>
+#include <linux/rculist.h>
#include <linux/uaccess.h>
#include <linux/kernel.h>
static unsigned int l2tp_net_id;
struct l2tp_net {
struct list_head l2tp_tunnel_list;
- rwlock_t l2tp_tunnel_list_lock;
+ spinlock_t l2tp_tunnel_list_lock;
struct hlist_head l2tp_session_hlist[L2TP_HASH_SIZE_2];
- rwlock_t l2tp_session_hlist_lock;
+ spinlock_t l2tp_session_hlist_lock;
};
static inline struct l2tp_net *l2tp_pernet(struct net *net)
struct l2tp_session *session;
struct hlist_node *walk;
- read_lock_bh(&pn->l2tp_session_hlist_lock);
- hlist_for_each_entry(session, walk, session_list, global_hlist) {
+ rcu_read_lock_bh();
+ hlist_for_each_entry_rcu(session, walk, session_list, global_hlist) {
if (session->session_id == session_id) {
- read_unlock_bh(&pn->l2tp_session_hlist_lock);
+ rcu_read_unlock_bh();
return session;
}
}
- read_unlock_bh(&pn->l2tp_session_hlist_lock);
+ rcu_read_unlock_bh();
return NULL;
}
struct hlist_node *walk;
struct l2tp_session *session;
- read_lock_bh(&pn->l2tp_session_hlist_lock);
+ rcu_read_lock_bh();
for (hash = 0; hash < L2TP_HASH_SIZE_2; hash++) {
- hlist_for_each_entry(session, walk, &pn->l2tp_session_hlist[hash], global_hlist) {
+ hlist_for_each_entry_rcu(session, walk, &pn->l2tp_session_hlist[hash], global_hlist) {
if (!strcmp(session->ifname, ifname)) {
- read_unlock_bh(&pn->l2tp_session_hlist_lock);
+ rcu_read_unlock_bh();
return session;
}
}
}
- read_unlock_bh(&pn->l2tp_session_hlist_lock);
+ rcu_read_unlock_bh();
return NULL;
}
struct l2tp_tunnel *tunnel;
struct l2tp_net *pn = l2tp_pernet(net);
- read_lock_bh(&pn->l2tp_tunnel_list_lock);
- list_for_each_entry(tunnel, &pn->l2tp_tunnel_list, list) {
+ rcu_read_lock_bh();
+ list_for_each_entry_rcu(tunnel, &pn->l2tp_tunnel_list, list) {
if (tunnel->tunnel_id == tunnel_id) {
- read_unlock_bh(&pn->l2tp_tunnel_list_lock);
+ rcu_read_unlock_bh();
return tunnel;
}
}
- read_unlock_bh(&pn->l2tp_tunnel_list_lock);
+ rcu_read_unlock_bh();
return NULL;
}
struct l2tp_tunnel *tunnel;
int count = 0;
- read_lock_bh(&pn->l2tp_tunnel_list_lock);
- list_for_each_entry(tunnel, &pn->l2tp_tunnel_list, list) {
+ rcu_read_lock_bh();
+ list_for_each_entry_rcu(tunnel, &pn->l2tp_tunnel_list, list) {
if (++count > nth) {
- read_unlock_bh(&pn->l2tp_tunnel_list_lock);
+ rcu_read_unlock_bh();
return tunnel;
}
}
- read_unlock_bh(&pn->l2tp_tunnel_list_lock);
+ rcu_read_unlock_bh();
return NULL;
}
}
/* Queue the packet to IP for output */
- error = ip_queue_xmit(skb, 1);
+ skb->local_df = 1;
+ error = ip_queue_xmit(skb);
/* Update stats */
if (error >= 0) {
if (tunnel->version != L2TP_HDR_VER_2) {
struct l2tp_net *pn = l2tp_pernet(tunnel->l2tp_net);
- write_lock_bh(&pn->l2tp_session_hlist_lock);
- hlist_del_init(&session->global_hlist);
- write_unlock_bh(&pn->l2tp_session_hlist_lock);
+ spin_lock_bh(&pn->l2tp_session_hlist_lock);
+ hlist_del_init_rcu(&session->global_hlist);
+ spin_unlock_bh(&pn->l2tp_session_hlist_lock);
+ synchronize_rcu();
}
if (session->session_close != NULL)
"%s: free...\n", tunnel->name);
/* Remove from tunnel list */
- write_lock_bh(&pn->l2tp_tunnel_list_lock);
- list_del_init(&tunnel->list);
- write_unlock_bh(&pn->l2tp_tunnel_list_lock);
+ spin_lock_bh(&pn->l2tp_tunnel_list_lock);
+ list_del_rcu(&tunnel->list);
+ spin_unlock_bh(&pn->l2tp_tunnel_list_lock);
+ synchronize_rcu();
atomic_dec(&l2tp_tunnel_count);
kfree(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 = NULL;
+
+ 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;
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;
/* Add tunnel to our list */
INIT_LIST_HEAD(&tunnel->list);
- write_lock_bh(&pn->l2tp_tunnel_list_lock);
- list_add(&tunnel->list, &pn->l2tp_tunnel_list);
- write_unlock_bh(&pn->l2tp_tunnel_list_lock);
+ spin_lock_bh(&pn->l2tp_tunnel_list_lock);
+ list_add_rcu(&tunnel->list, &pn->l2tp_tunnel_list);
+ spin_unlock_bh(&pn->l2tp_tunnel_list_lock);
+ synchronize_rcu();
atomic_inc(&l2tp_tunnel_count);
/* Bump the reference count. The tunnel context is deleted
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;
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;
}
if (tunnel->version != L2TP_HDR_VER_2) {
struct l2tp_net *pn = l2tp_pernet(tunnel->l2tp_net);
- write_lock_bh(&pn->l2tp_session_hlist_lock);
- hlist_del_init(&session->global_hlist);
- write_unlock_bh(&pn->l2tp_session_hlist_lock);
+ spin_lock_bh(&pn->l2tp_session_hlist_lock);
+ hlist_del_init_rcu(&session->global_hlist);
+ spin_unlock_bh(&pn->l2tp_session_hlist_lock);
+ synchronize_rcu();
}
if (session->session_id != 0)
if (tunnel->version != L2TP_HDR_VER_2) {
struct l2tp_net *pn = l2tp_pernet(tunnel->l2tp_net);
- write_lock_bh(&pn->l2tp_session_hlist_lock);
- hlist_add_head(&session->global_hlist,
- l2tp_session_id_hash_2(pn, session_id));
- write_unlock_bh(&pn->l2tp_session_hlist_lock);
+ spin_lock_bh(&pn->l2tp_session_hlist_lock);
+ hlist_add_head_rcu(&session->global_hlist,
+ l2tp_session_id_hash_2(pn, session_id));
+ spin_unlock_bh(&pn->l2tp_session_hlist_lock);
+ synchronize_rcu();
}
/* Ignore management session in session count value */
return -ENOMEM;
INIT_LIST_HEAD(&pn->l2tp_tunnel_list);
- rwlock_init(&pn->l2tp_tunnel_list_lock);
+ spin_lock_init(&pn->l2tp_tunnel_list_lock);
for (hash = 0; hash < L2TP_HASH_SIZE_2; hash++)
INIT_HLIST_HEAD(&pn->l2tp_session_hlist[hash]);
- rwlock_init(&pn->l2tp_session_hlist_lock);
+ spin_lock_init(&pn->l2tp_session_hlist_lock);
err = net_assign_generic(net, l2tp_net_id, pn);
if (err)