>Subject: [RFC net-next 1/2] tipc: abort pending connects when the peer node is
>lost
>
>From: liushike <[email protected]>
>
>A client can send a SYN and block in connect() while the listening server has
>not yet called accept(). If the last usable link to that server is lost, the 
>client may
>remain asleep until its connection timeout expires.
>Only established connections are in the peer node's conn_sks list, so
>node_lost_contact() cannot notify the pending connection.

Do retrying calling connect() and setting connection timeout to link tolerance 
(default: 1500 ms) help your use case ?

>
>Register an active open before sending SYN and check node availability under
>the node write lock. Let the existing node-loss notification abort the pending
>handshake with EHOSTUNREACH and wake the connect waiter.
>Losing one link does not abort the connect while another link survives.
>
>Update the listening port to the accepted port in place on receipt of ACK,
>preserving the node-loss registration. Reject a late ACK if node loss already
>removed the registration, even if the node has recovered.
>Transfer the registration when a named SYN is rerouted to another node.
>Avoid registering active opens twice; remove registrations on transmit failure
>and terminal rejection, but retain them across overload retries and
>connection-wait timeouts, which do not cancel the handshake.
>
>Signed-off-by: liushike <[email protected]>
>---
> net/tipc/node.c   | 37 ++++++++++++++++++++++++++++++++++++-
> net/tipc/node.h   |  1 +
> net/tipc/socket.c | 38 +++++++++++++++++++++++++++++++++++---
> 3 files changed, 72 insertions(+), 4 deletions(-)
>
>diff --git a/net/tipc/node.c b/net/tipc/node.c index
>bd91378b7540..31f2ef44ad37 100644
>--- a/net/tipc/node.c
>+++ b/net/tipc/node.c
>@@ -713,13 +713,48 @@ int tipc_node_add_conn(struct net *net, u32 dnode,
>u32 port, u32 peer_port)
>       conn->peer_port = peer_port;
>
>       tipc_node_write_lock(node);
>-      list_add_tail(&conn->list, &node->conn_sks);
>+      if (node_is_up(node)) {
>+              list_add_tail(&conn->list, &node->conn_sks);
>+      } else {
>+              err = -EHOSTUNREACH;
>+              kfree(conn);
>+      }
>       tipc_node_write_unlock(node);
> exit:
>       tipc_node_put(node);
>       return err;
> }
>
>+/* Replace the listening port with the accepted port without losing the
>+ * node loss subscription. A missing entry means node_lost_contact()
>+has
>+ * already aborted this connection attempt, even if the node is up again.
>+ */
>+int tipc_node_update_conn(struct net *net, u32 dnode, u32 port, u32
>+peer_port) {
>+      struct tipc_sock_conn *conn;
>+      struct tipc_node *node;
>+      int err = -EHOSTUNREACH;
>+
>+      if (in_own_node(net, dnode))
>+              return 0;
>+
>+      node = tipc_node_find(net, dnode);
>+      if (!node)
>+              return err;
>+
>+      tipc_node_write_lock(node);
>+      list_for_each_entry(conn, &node->conn_sks, list) {

Traversing the list of thousands of connections look bad.
Have you tried to measure performance regression when one socket of this node 
is sending/receiving messages and 4000 thousand other connections are initiated 
simultaneously ?

>+              if (conn->port != port)
>+                      continue;
>+              conn->peer_port = peer_port;
>+              err = 0;
>+              break;
>+      }
>+      tipc_node_write_unlock(node);
>+      tipc_node_put(node);
>+      return err;
>+}
>+
> void tipc_node_remove_conn(struct net *net, u32 dnode, u32 port)  {
>       struct tipc_node *node;
>diff --git a/net/tipc/node.h b/net/tipc/node.h index
>154a5bbb0d29..48693a050aa8 100644
>--- a/net/tipc/node.h
>+++ b/net/tipc/node.h
>@@ -107,6 +107,7 @@ void tipc_node_subscribe(struct net *net, struct
>list_head *subscr, u32 addr);  void tipc_node_unsubscribe(struct net *net,
>struct list_head *subscr, u32 addr);  void tipc_node_broadcast(struct net *net,
>struct sk_buff *skb, int rc_dests);  int tipc_node_add_conn(struct net *net, 
>u32
>dnode, u32 port, u32 peer_port);
>+int tipc_node_update_conn(struct net *net, u32 dnode, u32 port, u32
>+peer_port);
> void tipc_node_remove_conn(struct net *net, u32 dnode, u32 port);  int
>tipc_node_get_mtu(struct net *net, u32 addr, u32 sel, bool connected);  bool
>tipc_node_is_up(struct net *net, u32 addr); diff --git a/net/tipc/socket.c
>b/net/tipc/socket.c index d5d70eb230b5..191579636351 100644
>--- a/net/tipc/socket.c
>+++ b/net/tipc/socket.c
>@@ -1511,6 +1511,16 @@ static int __tipc_sendmsg(struct socket *sock, struct
>msghdr *m, size_t dlen)
>               return -ENOMEM;
>       }
>
>+      /* Subscribe before sending SYN so node loss also aborts pending
>connects. */
>+      if (syn) {
>+              rc = tipc_node_add_conn(net, skaddr.node, tsk->portid,
>skaddr.ref);
>+              if (rc) {
>+                      __skb_queue_purge(&pkts);
>+                      __skb_queue_purge(&sk->sk_write_queue);
>+                      return rc;
>+              }
>+      }
>+
>       /* Send message */
>       trace_tipc_sk_sendmsg(sk, skb_peek(&pkts), TIPC_DUMP_SK_SNDQ, "
>");
>       rc = tipc_node_xmit(net, &pkts, skaddr.node, tsk->portid); @@ -1520,6
>+1530,11 @@ static int __tipc_sendmsg(struct socket *sock, struct msghdr *m,
>size_t dlen)
>               rc = 0;
>       }
>
>+      if (unlikely(syn && rc)) {
>+              tipc_node_remove_conn(net, skaddr.node, tsk->portid);
>+              __skb_queue_purge(&sk->sk_write_queue);
>+      }
>+
>       if (unlikely(syn && !rc)) {
>               tipc_set_sk_state(sk, TIPC_CONNECTING);
>               if (dlen && timeout) {
>@@ -1674,8 +1689,10 @@ static void tipc_sk_finish_conn(struct tipc_sock
>*tsk, u32 peer_port,
>       msg_set_hdr_sz(msg, SHORT_H_SIZE);
>
>       sk_reset_timer(sk, &sk->sk_timer, jiffies + CONN_PROBING_INTV);
>+      /* Active opens already subscribed before sending SYN. */
>+      if (sk->sk_state != TIPC_CONNECTING)
>+              tipc_node_add_conn(net, peer_node, tsk->portid, peer_port);
>       tipc_set_sk_state(sk, TIPC_ESTABLISHED);
>-      tipc_node_add_conn(net, peer_node, tsk->portid, peer_port);
>       tsk->max_pkt = tipc_node_get_mtu(net, peer_node, tsk->portid, true);
>       tsk->peer_caps = tipc_node_get_capabilities(net, peer_node);
>       tsk_set_nagle(tsk);
>@@ -2199,12 +2216,14 @@ static bool tipc_sk_filter_connect(struct tipc_sock
>*tsk, struct sk_buff *skb,
>       struct net *net = sock_net(sk);
>       struct tipc_msg *hdr = buf_msg(skb);
>       bool con_msg = msg_connected(hdr);
>+      bool reject_ack = false;
>       u32 pport = tsk_peer_port(tsk);
>       u32 pnode = tsk_peer_node(tsk);
>       u32 oport = msg_origport(hdr);
>       u32 onode = msg_orignode(hdr);
>       int err = msg_errcode(hdr);
>       unsigned long delay;
>+      int rc;
>
>       if (unlikely(msg_mcast(hdr)))
>               return false;
>@@ -2216,6 +2235,18 @@ static bool tipc_sk_filter_connect(struct tipc_sock
>*tsk, struct sk_buff *skb,
>               if (likely(con_msg)) {
>                       if (err)
>                               break;
>+                      rc = tipc_node_update_conn(net, pnode, tsk->portid,
>oport);
>+                      /* A named SYN may have been rerouted to another
>node. */
>+                      if (!rc && onode != pnode) {
>+                              tipc_node_remove_conn(net, pnode, tsk-
>>portid);
>+                              rc = tipc_node_add_conn(net, onode, tsk-
>>portid, oport);
>+                      }
>+                      if (rc) {
>+                              err = TIPC_ERR_NO_NODE;
>+                              /* Return a late ACK to its sender, including 
>any
>data. */
>+                              reject_ack = true;
>+                              break;
>+                      }
>                       tipc_sk_finish_conn(tsk, oport, onode);
>                       msg_set_importance(&tsk->phdr,
>msg_importance(hdr));
>                       /* ACK+ message with data is added to receive queue
>*/ @@ -2285,9 +2316,10 @@ static bool tipc_sk_filter_connect(struct
>tipc_sock *tsk, struct sk_buff *skb,
>       }
>       /* Abort connection setup attempt */
>       tipc_set_sk_state(sk, TIPC_DISCONNECTING);
>-      sk->sk_err = ECONNREFUSED;
>+      tipc_node_remove_conn(net, pnode, tsk->portid);
>+      sk->sk_err = err == TIPC_ERR_NO_NODE ? EHOSTUNREACH :
>ECONNREFUSED;
>       sk->sk_state_change(sk);
>-      return true;
>+      return !reject_ack;
> }
>
> /**
>--
>2.34.1


Reply via email to