|
@@ -122,8 +122,11 @@
|
|
|
struct pppol2tp_session {
|
|
|
int owner; /* pid that opened the socket */
|
|
|
|
|
|
- struct sock *sock; /* Pointer to the session
|
|
|
+ struct mutex sk_lock; /* Protects .sk */
|
|
|
+ struct sock __rcu *sk; /* Pointer to the session
|
|
|
* PPPoX socket */
|
|
|
+ struct sock *__sk; /* Copy of .sk, for cleanup */
|
|
|
+ struct rcu_head rcu; /* For asynchronous release */
|
|
|
struct sock *tunnel_sock; /* Pointer to the tunnel UDP
|
|
|
* socket */
|
|
|
int flags; /* accessed by PPPIOCGFLAGS.
|
|
@@ -138,6 +141,24 @@ static const struct ppp_channel_ops pppol2tp_chan_ops = {
|
|
|
|
|
|
static const struct proto_ops pppol2tp_ops;
|
|
|
|
|
|
+/* Retrieves the pppol2tp socket associated to a session.
|
|
|
+ * A reference is held on the returned socket, so this function must be paired
|
|
|
+ * with sock_put().
|
|
|
+ */
|
|
|
+static struct sock *pppol2tp_session_get_sock(struct l2tp_session *session)
|
|
|
+{
|
|
|
+ struct pppol2tp_session *ps = l2tp_session_priv(session);
|
|
|
+ struct sock *sk;
|
|
|
+
|
|
|
+ rcu_read_lock();
|
|
|
+ sk = rcu_dereference(ps->sk);
|
|
|
+ if (sk)
|
|
|
+ sock_hold(sk);
|
|
|
+ rcu_read_unlock();
|
|
|
+
|
|
|
+ return sk;
|
|
|
+}
|
|
|
+
|
|
|
/* Helpers to obtain tunnel/session contexts from sockets.
|
|
|
*/
|
|
|
static inline struct l2tp_session *pppol2tp_sock_to_session(struct sock *sk)
|
|
@@ -224,7 +245,8 @@ static void pppol2tp_recv(struct l2tp_session *session, struct sk_buff *skb, int
|
|
|
/* If the socket is bound, send it in to PPP's input queue. Otherwise
|
|
|
* queue it on the session socket.
|
|
|
*/
|
|
|
- sk = ps->sock;
|
|
|
+ rcu_read_lock();
|
|
|
+ sk = rcu_dereference(ps->sk);
|
|
|
if (sk == NULL)
|
|
|
goto no_sock;
|
|
|
|
|
@@ -247,30 +269,16 @@ static void pppol2tp_recv(struct l2tp_session *session, struct sk_buff *skb, int
|
|
|
kfree_skb(skb);
|
|
|
}
|
|
|
}
|
|
|
+ rcu_read_unlock();
|
|
|
|
|
|
return;
|
|
|
|
|
|
no_sock:
|
|
|
+ rcu_read_unlock();
|
|
|
l2tp_info(session, L2TP_MSG_DATA, "%s: no socket\n", session->name);
|
|
|
kfree_skb(skb);
|
|
|
}
|
|
|
|
|
|
-static void pppol2tp_session_sock_hold(struct l2tp_session *session)
|
|
|
-{
|
|
|
- struct pppol2tp_session *ps = l2tp_session_priv(session);
|
|
|
-
|
|
|
- if (ps->sock)
|
|
|
- sock_hold(ps->sock);
|
|
|
-}
|
|
|
-
|
|
|
-static void pppol2tp_session_sock_put(struct l2tp_session *session)
|
|
|
-{
|
|
|
- struct pppol2tp_session *ps = l2tp_session_priv(session);
|
|
|
-
|
|
|
- if (ps->sock)
|
|
|
- sock_put(ps->sock);
|
|
|
-}
|
|
|
-
|
|
|
/************************************************************************
|
|
|
* Transmit handling
|
|
|
***********************************************************************/
|
|
@@ -431,14 +439,16 @@ abort:
|
|
|
*/
|
|
|
static void pppol2tp_session_close(struct l2tp_session *session)
|
|
|
{
|
|
|
- struct pppol2tp_session *ps = l2tp_session_priv(session);
|
|
|
- struct sock *sk = ps->sock;
|
|
|
- struct socket *sock = sk->sk_socket;
|
|
|
+ struct sock *sk;
|
|
|
|
|
|
BUG_ON(session->magic != L2TP_SESSION_MAGIC);
|
|
|
|
|
|
- if (sock)
|
|
|
- inet_shutdown(sock, SEND_SHUTDOWN);
|
|
|
+ sk = pppol2tp_session_get_sock(session);
|
|
|
+ if (sk) {
|
|
|
+ if (sk->sk_socket)
|
|
|
+ inet_shutdown(sk->sk_socket, SEND_SHUTDOWN);
|
|
|
+ sock_put(sk);
|
|
|
+ }
|
|
|
|
|
|
/* Don't let the session go away before our socket does */
|
|
|
l2tp_session_inc_refcount(session);
|
|
@@ -461,6 +471,14 @@ static void pppol2tp_session_destruct(struct sock *sk)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+static void pppol2tp_put_sk(struct rcu_head *head)
|
|
|
+{
|
|
|
+ struct pppol2tp_session *ps;
|
|
|
+
|
|
|
+ ps = container_of(head, typeof(*ps), rcu);
|
|
|
+ sock_put(ps->__sk);
|
|
|
+}
|
|
|
+
|
|
|
/* Called when the PPPoX socket (session) is closed.
|
|
|
*/
|
|
|
static int pppol2tp_release(struct socket *sock)
|
|
@@ -486,11 +504,24 @@ static int pppol2tp_release(struct socket *sock)
|
|
|
|
|
|
session = pppol2tp_sock_to_session(sk);
|
|
|
|
|
|
- /* Purge any queued data */
|
|
|
if (session != NULL) {
|
|
|
+ struct pppol2tp_session *ps;
|
|
|
+
|
|
|
__l2tp_session_unhash(session);
|
|
|
l2tp_session_queue_purge(session);
|
|
|
- sock_put(sk);
|
|
|
+
|
|
|
+ ps = l2tp_session_priv(session);
|
|
|
+ mutex_lock(&ps->sk_lock);
|
|
|
+ ps->__sk = rcu_dereference_protected(ps->sk,
|
|
|
+ lockdep_is_held(&ps->sk_lock));
|
|
|
+ RCU_INIT_POINTER(ps->sk, NULL);
|
|
|
+ mutex_unlock(&ps->sk_lock);
|
|
|
+ call_rcu(&ps->rcu, pppol2tp_put_sk);
|
|
|
+
|
|
|
+ /* Rely on the sock_put() call at the end of the function for
|
|
|
+ * dropping the reference held by pppol2tp_sock_to_session().
|
|
|
+ * The last reference will be dropped by pppol2tp_put_sk().
|
|
|
+ */
|
|
|
}
|
|
|
release_sock(sk);
|
|
|
|
|
@@ -557,12 +588,14 @@ out:
|
|
|
static void pppol2tp_show(struct seq_file *m, void *arg)
|
|
|
{
|
|
|
struct l2tp_session *session = arg;
|
|
|
- struct pppol2tp_session *ps = l2tp_session_priv(session);
|
|
|
+ struct sock *sk;
|
|
|
+
|
|
|
+ sk = pppol2tp_session_get_sock(session);
|
|
|
+ if (sk) {
|
|
|
+ struct pppox_sock *po = pppox_sk(sk);
|
|
|
|
|
|
- if (ps) {
|
|
|
- struct pppox_sock *po = pppox_sk(ps->sock);
|
|
|
- if (po)
|
|
|
- seq_printf(m, " interface %s\n", ppp_dev_name(&po->chan));
|
|
|
+ seq_printf(m, " interface %s\n", ppp_dev_name(&po->chan));
|
|
|
+ sock_put(sk);
|
|
|
}
|
|
|
}
|
|
|
#endif
|
|
@@ -693,13 +726,17 @@ static int pppol2tp_connect(struct socket *sock, struct sockaddr *uservaddr,
|
|
|
/* Using a pre-existing session is fine as long as it hasn't
|
|
|
* been connected yet.
|
|
|
*/
|
|
|
- if (ps->sock) {
|
|
|
+ mutex_lock(&ps->sk_lock);
|
|
|
+ if (rcu_dereference_protected(ps->sk,
|
|
|
+ lockdep_is_held(&ps->sk_lock))) {
|
|
|
+ mutex_unlock(&ps->sk_lock);
|
|
|
error = -EEXIST;
|
|
|
goto end;
|
|
|
}
|
|
|
|
|
|
/* consistency checks */
|
|
|
if (ps->tunnel_sock != tunnel->sock) {
|
|
|
+ mutex_unlock(&ps->sk_lock);
|
|
|
error = -EEXIST;
|
|
|
goto end;
|
|
|
}
|
|
@@ -716,19 +753,21 @@ static int pppol2tp_connect(struct socket *sock, struct sockaddr *uservaddr,
|
|
|
goto end;
|
|
|
}
|
|
|
|
|
|
+ ps = l2tp_session_priv(session);
|
|
|
+ mutex_init(&ps->sk_lock);
|
|
|
l2tp_session_inc_refcount(session);
|
|
|
+
|
|
|
+ mutex_lock(&ps->sk_lock);
|
|
|
error = l2tp_session_register(session, tunnel);
|
|
|
if (error < 0) {
|
|
|
+ mutex_unlock(&ps->sk_lock);
|
|
|
kfree(session);
|
|
|
goto end;
|
|
|
}
|
|
|
drop_refcnt = true;
|
|
|
}
|
|
|
|
|
|
- /* Associate session with its PPPoL2TP socket */
|
|
|
- ps = l2tp_session_priv(session);
|
|
|
ps->owner = current->pid;
|
|
|
- ps->sock = sk;
|
|
|
ps->tunnel_sock = tunnel->sock;
|
|
|
|
|
|
session->recv_skb = pppol2tp_recv;
|
|
@@ -737,12 +776,6 @@ static int pppol2tp_connect(struct socket *sock, struct sockaddr *uservaddr,
|
|
|
session->show = pppol2tp_show;
|
|
|
#endif
|
|
|
|
|
|
- /* We need to know each time a skb is dropped from the reorder
|
|
|
- * queue.
|
|
|
- */
|
|
|
- session->ref = pppol2tp_session_sock_hold;
|
|
|
- session->deref = pppol2tp_session_sock_put;
|
|
|
-
|
|
|
/* If PMTU discovery was enabled, use the MTU that was discovered */
|
|
|
dst = sk_dst_get(tunnel->sock);
|
|
|
if (dst != NULL) {
|
|
@@ -776,12 +809,17 @@ static int pppol2tp_connect(struct socket *sock, struct sockaddr *uservaddr,
|
|
|
po->chan.mtu = session->mtu;
|
|
|
|
|
|
error = ppp_register_net_channel(sock_net(sk), &po->chan);
|
|
|
- if (error)
|
|
|
+ if (error) {
|
|
|
+ mutex_unlock(&ps->sk_lock);
|
|
|
goto end;
|
|
|
+ }
|
|
|
|
|
|
out_no_ppp:
|
|
|
/* This is how we get the session context from the socket. */
|
|
|
sk->sk_user_data = session;
|
|
|
+ rcu_assign_pointer(ps->sk, sk);
|
|
|
+ mutex_unlock(&ps->sk_lock);
|
|
|
+
|
|
|
sk->sk_state = PPPOX_CONNECTED;
|
|
|
l2tp_info(session, L2TP_MSG_CONTROL, "%s: created\n",
|
|
|
session->name);
|
|
@@ -827,6 +865,7 @@ static int pppol2tp_session_create(struct net *net, struct l2tp_tunnel *tunnel,
|
|
|
}
|
|
|
|
|
|
ps = l2tp_session_priv(session);
|
|
|
+ mutex_init(&ps->sk_lock);
|
|
|
ps->tunnel_sock = tunnel->sock;
|
|
|
|
|
|
error = l2tp_session_register(session, tunnel);
|
|
@@ -998,12 +1037,10 @@ static int pppol2tp_session_ioctl(struct l2tp_session *session,
|
|
|
"%s: pppol2tp_session_ioctl(cmd=%#x, arg=%#lx)\n",
|
|
|
session->name, cmd, arg);
|
|
|
|
|
|
- sk = ps->sock;
|
|
|
+ sk = pppol2tp_session_get_sock(session);
|
|
|
if (!sk)
|
|
|
return -EBADR;
|
|
|
|
|
|
- sock_hold(sk);
|
|
|
-
|
|
|
switch (cmd) {
|
|
|
case SIOCGIFMTU:
|
|
|
err = -ENXIO;
|
|
@@ -1279,7 +1316,6 @@ static int pppol2tp_session_setsockopt(struct sock *sk,
|
|
|
int optname, int val)
|
|
|
{
|
|
|
int err = 0;
|
|
|
- struct pppol2tp_session *ps = l2tp_session_priv(session);
|
|
|
|
|
|
switch (optname) {
|
|
|
case PPPOL2TP_SO_RECVSEQ:
|
|
@@ -1300,8 +1336,8 @@ static int pppol2tp_session_setsockopt(struct sock *sk,
|
|
|
}
|
|
|
session->send_seq = !!val;
|
|
|
{
|
|
|
- struct sock *ssk = ps->sock;
|
|
|
- struct pppox_sock *po = pppox_sk(ssk);
|
|
|
+ struct pppox_sock *po = pppox_sk(sk);
|
|
|
+
|
|
|
po->chan.hdrlen = val ? PPPOL2TP_L2TP_HDR_SIZE_SEQ :
|
|
|
PPPOL2TP_L2TP_HDR_SIZE_NOSEQ;
|
|
|
}
|
|
@@ -1640,8 +1676,9 @@ static void pppol2tp_seq_session_show(struct seq_file *m, void *v)
|
|
|
{
|
|
|
struct l2tp_session *session = v;
|
|
|
struct l2tp_tunnel *tunnel = session->tunnel;
|
|
|
- struct pppol2tp_session *ps = l2tp_session_priv(session);
|
|
|
- struct pppox_sock *po = pppox_sk(ps->sock);
|
|
|
+ unsigned char state;
|
|
|
+ char user_data_ok;
|
|
|
+ struct sock *sk;
|
|
|
u32 ip = 0;
|
|
|
u16 port = 0;
|
|
|
|
|
@@ -1651,6 +1688,15 @@ static void pppol2tp_seq_session_show(struct seq_file *m, void *v)
|
|
|
port = ntohs(inet->inet_sport);
|
|
|
}
|
|
|
|
|
|
+ sk = pppol2tp_session_get_sock(session);
|
|
|
+ if (sk) {
|
|
|
+ state = sk->sk_state;
|
|
|
+ user_data_ok = (session == sk->sk_user_data) ? 'Y' : 'N';
|
|
|
+ } else {
|
|
|
+ state = 0;
|
|
|
+ user_data_ok = 'N';
|
|
|
+ }
|
|
|
+
|
|
|
seq_printf(m, " SESSION '%s' %08X/%d %04X/%04X -> "
|
|
|
"%04X/%04X %d %c\n",
|
|
|
session->name, ip, port,
|
|
@@ -1658,9 +1704,7 @@ static void pppol2tp_seq_session_show(struct seq_file *m, void *v)
|
|
|
session->session_id,
|
|
|
tunnel->peer_tunnel_id,
|
|
|
session->peer_session_id,
|
|
|
- ps->sock->sk_state,
|
|
|
- (session == ps->sock->sk_user_data) ?
|
|
|
- 'Y' : 'N');
|
|
|
+ state, user_data_ok);
|
|
|
seq_printf(m, " %d/%d/%c/%c/%s %08x %u\n",
|
|
|
session->mtu, session->mru,
|
|
|
session->recv_seq ? 'R' : '-',
|
|
@@ -1677,8 +1721,12 @@ static void pppol2tp_seq_session_show(struct seq_file *m, void *v)
|
|
|
atomic_long_read(&session->stats.rx_bytes),
|
|
|
atomic_long_read(&session->stats.rx_errors));
|
|
|
|
|
|
- if (po)
|
|
|
+ if (sk) {
|
|
|
+ struct pppox_sock *po = pppox_sk(sk);
|
|
|
+
|
|
|
seq_printf(m, " interface %s\n", ppp_dev_name(&po->chan));
|
|
|
+ sock_put(sk);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
static int pppol2tp_seq_show(struct seq_file *m, void *v)
|