Commit 1a0e100f authored by Sowmini Varadhan's avatar Sowmini Varadhan Committed by David S. Miller

RDS: TCP: Force every connection to be initiated by numerically smaller IP address

When 2 RDS peers initiate an RDS-TCP connection simultaneously,
there is a potential for "duelling syns" on either/both sides.
See commit 241b2719 ("RDS-TCP: Reset tcp callbacks if re-using an
outgoing socket in rds_tcp_accept_one()") for a description of this
condition, and the arbitration logic which ensures that the
numerically large IP address in the TCP connection is bound to the
RDS_TCP_PORT ("canonical ordering").

The rds_connection should not be marked as RDS_CONN_UP until the
arbitration logic has converged for the following reason. The sender
may start transmitting RDS datagrams as soon as RDS_CONN_UP is set,
and since the sender removes all datagrams from the rds_connection's
cp_retrans queue based on TCP acks. If the TCP ack was sent from
a tcp socket that got reset as part of duel aribitration (but
before data was delivered to the receivers RDS socket layer),
the sender may end up prematurely freeing the datagram, and
the datagram is no longer reliably deliverable.

This patch remedies that condition by making sure that, upon
receipt of 3WH completion state change notification of TCP_ESTABLISHED
in rds_tcp_state_change, we mark the rds_connection as RDS_CONN_UP
if, and only if, the IP addresses and ports for the connection are
canonically ordered. In all other cases, rds_tcp_state_change will
force an rds_conn_path_drop(), and rds_queue_reconnect() on
both peers will restart the connection to ensure canonical ordering.

A side-effect of enforcing this condition in rds_tcp_state_change()
is that rds_tcp_accept_one_path() can now be refactored for simplicity.
It is also no longer possible to encounter an RDS_CONN_UP connection in
the arbitration logic in rds_tcp_accept_one().
Signed-off-by: default avatarSowmini Varadhan <sowmini.varadhan@oracle.com>
Acked-by: default avatarSantosh Shilimkar <santosh.shilimkar@oracle.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 905dd418
...@@ -683,6 +683,7 @@ void rds_conn_path_connect_if_down(struct rds_conn_path *cp) ...@@ -683,6 +683,7 @@ void rds_conn_path_connect_if_down(struct rds_conn_path *cp)
!test_and_set_bit(RDS_RECONNECT_PENDING, &cp->cp_flags)) !test_and_set_bit(RDS_RECONNECT_PENDING, &cp->cp_flags))
queue_delayed_work(rds_wq, &cp->cp_conn_w, 0); queue_delayed_work(rds_wq, &cp->cp_conn_w, 0);
} }
EXPORT_SYMBOL_GPL(rds_conn_path_connect_if_down);
void rds_conn_connect_if_down(struct rds_connection *conn) void rds_conn_connect_if_down(struct rds_connection *conn)
{ {
......
...@@ -60,7 +60,19 @@ void rds_tcp_state_change(struct sock *sk) ...@@ -60,7 +60,19 @@ void rds_tcp_state_change(struct sock *sk)
case TCP_SYN_RECV: case TCP_SYN_RECV:
break; break;
case TCP_ESTABLISHED: case TCP_ESTABLISHED:
rds_connect_path_complete(cp, RDS_CONN_CONNECTING); /* Force the peer to reconnect so that we have the
* TCP ports going from <smaller-ip>.<transient> to
* <larger-ip>.<RDS_TCP_PORT>. We avoid marking the
* RDS connection as RDS_CONN_UP until the reconnect,
* to avoid RDS datagram loss.
*/
if (cp->cp_conn->c_laddr > cp->cp_conn->c_faddr &&
rds_conn_path_transition(cp, RDS_CONN_CONNECTING,
RDS_CONN_ERROR)) {
rds_conn_path_drop(cp);
} else {
rds_connect_path_complete(cp, RDS_CONN_CONNECTING);
}
break; break;
case TCP_CLOSE_WAIT: case TCP_CLOSE_WAIT:
case TCP_CLOSE: case TCP_CLOSE:
......
...@@ -83,25 +83,20 @@ struct rds_tcp_connection *rds_tcp_accept_one_path(struct rds_connection *conn) ...@@ -83,25 +83,20 @@ struct rds_tcp_connection *rds_tcp_accept_one_path(struct rds_connection *conn)
{ {
int i; int i;
bool peer_is_smaller = (conn->c_faddr < conn->c_laddr); bool peer_is_smaller = (conn->c_faddr < conn->c_laddr);
int npaths = conn->c_npaths; int npaths = max_t(int, 1, conn->c_npaths);
if (npaths <= 1) {
struct rds_conn_path *cp = &conn->c_path[0];
int ret;
ret = rds_conn_path_transition(cp, RDS_CONN_DOWN,
RDS_CONN_CONNECTING);
if (!ret)
rds_conn_path_transition(cp, RDS_CONN_ERROR,
RDS_CONN_CONNECTING);
return cp->cp_transport_data;
}
/* for mprds, paths with cp_index > 0 MUST be initiated by the peer /* for mprds, all paths MUST be initiated by the peer
* with the smaller address. * with the smaller address.
*/ */
if (!peer_is_smaller) if (!peer_is_smaller) {
/* Make sure we initiate at least one path if this
* has not already been done; rds_start_mprds() will
* take care of additional paths, if necessary.
*/
if (npaths == 1)
rds_conn_path_connect_if_down(&conn->c_path[0]);
return NULL; return NULL;
}
for (i = 0; i < npaths; i++) { for (i = 0; i < npaths; i++) {
struct rds_conn_path *cp = &conn->c_path[i]; struct rds_conn_path *cp = &conn->c_path[i];
...@@ -171,8 +166,8 @@ int rds_tcp_accept_one(struct socket *sock) ...@@ -171,8 +166,8 @@ int rds_tcp_accept_one(struct socket *sock)
mutex_lock(&rs_tcp->t_conn_path_lock); mutex_lock(&rs_tcp->t_conn_path_lock);
cp = rs_tcp->t_cpath; cp = rs_tcp->t_cpath;
conn_state = rds_conn_path_state(cp); conn_state = rds_conn_path_state(cp);
if (conn_state != RDS_CONN_CONNECTING && conn_state != RDS_CONN_UP && WARN_ON(conn_state == RDS_CONN_UP);
conn_state != RDS_CONN_ERROR) if (conn_state != RDS_CONN_CONNECTING && conn_state != RDS_CONN_ERROR)
goto rst_nsk; goto rst_nsk;
if (rs_tcp->t_sock) { if (rs_tcp->t_sock) {
/* Need to resolve a duelling SYN between peers. /* Need to resolve a duelling SYN between peers.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment